Listening to Inbox Events

This tutorial explains how to listen to events from the Inbox in your application.

Overview

The Messaging extension provides ways to monitor:

Inbox-Level Events

The AepInbox composable requires an AepInboxEventObserver to handle inbox-level events such as when the inbox is displayed. The Messaging extension provides InboxEventObserver which implements this interface and automatically tracks inbox display events.

Using InboxEventObserver

Create an InboxEventObserver in your ViewModel with a reference to the provider:

data-slots=heading, code
data-repeat=1
data-languages=Kotlin

Kotlin

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.adobe.marketing.mobile.messaging.InboxEventObserver
import com.adobe.marketing.mobile.messaging.MessagingInboxProvider
import com.adobe.marketing.mobile.messaging.Surface
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn

class InboxViewModel : ViewModel() {
    val inboxProvider = MessagingInboxProvider(Surface("inbox"))

    val observer = InboxEventObserver(inboxProvider)

    val inboxUIState = inboxProvider.getInboxUI()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = InboxUIState.Loading
        )
}

@Composable
fun InboxScreen(viewModel: InboxViewModel) {
    val inboxUIState by viewModel.inboxUIState.collectAsStateWithLifecycle()

    AepInbox(
        uiState = inboxUIState,
        inboxStyle = InboxUIStyle.Builder().build(),
        observer = viewModel.observer
    )
}

The InboxEventObserver automatically:

Inbox State Changes

The InboxUIState sealed interface represents the different states of the inbox. By collecting the state flow from MessagingInboxProvider, you can respond to state transitions.

InboxUIState Types

State
Description
InboxUIState.Loading
The inbox is fetching content from the device cache
InboxUIState.Success
Content loaded successfully (may contain cards or be empty)
InboxUIState.Error
An error occurred while loading content

Observing State Changes

Use the Flow returned by getInboxUI() to observe state changes:

data-slots=heading, code
data-repeat=1
data-languages=Kotlin

Kotlin

class InboxViewModel : ViewModel() {
    val inboxProvider = MessagingInboxProvider(Surface("inbox"))

    val observer = InboxEventObserver(inboxProvider)

    val inboxUIState: StateFlow<InboxUIState> = inboxProvider.getInboxUI()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = InboxUIState.Loading
        )
}

Responding to State Changes in UI

You can respond to state changes directly in your composable:

data-slots=heading, code
data-repeat=1
data-languages=Kotlin

Kotlin

@Composable
fun InboxScreen(viewModel: InboxViewModel) {
    val inboxUIState by viewModel.inboxUIState.collectAsStateWithLifecycle()

    // Respond to state changes
    LaunchedEffect(inboxUIState) {
        when (inboxUIState) {
            is InboxUIState.Loading -> {
                Log.d("Inbox", "Loading inbox...")
            }
            is InboxUIState.Success -> {
                val successState = inboxUIState as InboxUIState.Success
                Log.d("Inbox", "Loaded ${successState.items.size} cards")
            }
            is InboxUIState.Error -> {
                val errorState = inboxUIState as InboxUIState.Error
                Log.e("Inbox", "Error: ${errorState.error.message}")
            }
        }
    }

    AepInbox(
        uiState = inboxUIState,
        inboxStyle = InboxUIStyle.Builder().build(),
        observer = viewModel.observer
    )
}

Accessing Success State Data

When the inbox loads successfully, InboxUIState.Success contains:

data-slots=heading, code
data-repeat=1
data-languages=Kotlin

Kotlin

when (val state = inboxUIState) {
    is InboxUIState.Success -> {
        val cardCount = state.items.size
        val heading = state.template.heading
        val layout = state.template.layout

        // Check if inbox is empty
        if (state.items.isEmpty()) {
            Log.d("Inbox", "Inbox is empty")
        }
    }
    // ...
}

Handling Errors

When an error occurs, InboxUIState.Error contains the throwable with error details:

data-slots=heading, code
data-repeat=1
data-languages=Kotlin

Kotlin

when (val state = inboxUIState) {
    is InboxUIState.Error -> {
        val errorMessage = state.error.message ?: "Unknown error"

        // Show error UI or retry
        showErrorDialog(errorMessage) {
            viewModel.refresh()
        }
    }
    // ...
}

Content Card Events

Individual content cards within the inbox emit events when users interact with them. To listen to these events:

  1. Implement a ContentCardUIEventListener to handle card events
  2. Wrap it in a ContentCardEventObserver
  3. Pass the ContentCardEventObserver to InboxEventObserver in your ViewModel

Example

data-slots=heading, code
data-repeat=1
data-languages=Kotlin

Kotlin

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.adobe.marketing.mobile.aepcomposeui.AepUI
import com.adobe.marketing.mobile.messaging.ContentCardEventObserver
import com.adobe.marketing.mobile.messaging.ContentCardUIEventListener
import com.adobe.marketing.mobile.messaging.InboxEventObserver
import com.adobe.marketing.mobile.messaging.MessagingInboxProvider
import com.adobe.marketing.mobile.messaging.Surface
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn

// In ViewModel - observer survives configuration changes
class InboxViewModel : ViewModel() {
    private val cardCallback = object : ContentCardUIEventListener {
        override fun onDisplay(aepUI: AepUI<*, *>) {
            // Handle card display
        }

        override fun onInteract(
            aepUI: AepUI<*, *>,
            interactionId: String?,
            actionUrl: String?
        ): Boolean {
            // Handle card interaction
            return false
        }

        override fun onDismiss(aepUI: AepUI<*, *>) {
            // Handle card dismissal
        }
    }

    val inboxProvider = MessagingInboxProvider(Surface("inbox"))

    val observer = InboxEventObserver(
        inboxProvider,
        ContentCardEventObserver(cardCallback)
    )

    val inboxUIState = inboxProvider.getInboxUI()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = InboxUIState.Loading
        )
}

@Composable
fun InboxScreen(viewModel: InboxViewModel) {
    val inboxUIState by viewModel.inboxUIState.collectAsStateWithLifecycle()

    AepInbox(
        uiState = inboxUIState,
        inboxStyle = InboxUIStyle.Builder().build(),
        observer = viewModel.observer
    )
}

For detailed information on content card events, implementing ContentCardUIEventListener, and handling actionable URLs, see Listening to Content Card Events.

Best Practices

  1. Create Observer in ViewModel: Always create the InboxEventObserver in your ViewModel with a reference to the provider. This ensures the observer survives configuration changes (screen rotation, theme changes, etc.):
data-slots=heading, code
data-repeat=1
data-languages=Kotlin

Kotlin

class InboxViewModel : ViewModel() {
    val inboxProvider = MessagingInboxProvider(Surface("inbox"))
    val observer = InboxEventObserver(inboxProvider)
}
  1. Avoid Heavy Work in Event Handlers: Event handlers are called on the main thread. Keep them lightweight and dispatch heavy work to background threads.

  2. Handle Errors Gracefully: Provide user-friendly error messages and retry options when the inbox fails to load.

  3. Use Lifecycle-aware Collection: Use collectAsStateWithLifecycle() to automatically stop collecting when the UI is not visible.