Edit in GitHubLog an issue

Displaying Inbox

This tutorial explains how to fetch and display an Inbox in your Android application.

Pre-requisites

Integrate and register AEPMessaging extension in your app.

Overview

The Inbox is a pre-built UI component that displays content cards in a unified container. Unlike individual content cards, the Inbox automatically manages loading states, error handling, empty states, and card layout based on server-side configuration from Adobe Journey Optimizer.

Fetch Inbox settings and Content Cards

To fetch the inbox settings and content cards for the surfaces configured in Adobe Journey Optimizer campaigns, call the updatePropositionsForSurfaces API. You should batch requests for multiple surfaces in a single API call when possible. The returned inbox settings and content cards are cached in-memory by the Messaging extension and persist through the application's lifecycle.

Copied to your clipboard
val surfaces = mutableListOf<Surface>()
surfaces.add(Surface("inbox"))
Messaging.updatePropositionsForSurfaces(surfaces)

Create MessagingInboxProvider

To display an Inbox, first create a MessagingInboxProvider with your configured surface. This provider is responsible for fetching inbox content and managing the inbox state through reactive updates.

Copied to your clipboard
import com.adobe.marketing.mobile.messaging.MessagingInboxProvider
import com.adobe.marketing.mobile.messaging.Surface
val surface = Surface("inbox")
val inboxProvider = MessagingInboxProvider(surface)

The MessagingInboxProvider provides the following APIs:

  • getInboxUI() - Returns a Flow of InboxUIState representing the current state of the inbox.
  • refresh() - Manually refreshes the inbox content by fetching new propositions.

Retrieve Inbox State

To retrieve the inbox state, create a MessagingInboxProvider in your ViewModel and call getInboxUI(). This returns a Flow of InboxUIState objects that represent different states of the inbox:

  • InboxUIState.Loading - Content is being fetched
  • InboxUIState.Success - Content was successfully loaded
  • InboxUIState.Error - An error occurred while fetching content
Copied to your clipboard
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.adobe.marketing.mobile.aepcomposeui.state.InboxUIState
import com.adobe.marketing.mobile.messaging.MessagingInboxProvider
import com.adobe.marketing.mobile.messaging.Surface
import com.adobe.marketing.mobile.messaging.InboxEventObserver
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class InboxViewModel : ViewModel() {
val inboxProvider = MessagingInboxProvider(Surface("inbox"))
val observer = InboxEventObserver(inboxProvider)
// Convert Flow to StateFlow for easier consumption in Compose
val inboxUIState: StateFlow<InboxUIState> = inboxProvider.getInboxUI()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = InboxUIState.Loading
)
// Function to manually refresh the inbox
fun refresh() {
viewModelScope.launch {
inboxProvider.refresh()
}
}
}

Display Inbox in Compose UI

The Inbox user interface is implemented using Jetpack Compose. To display the inbox, use the AepInbox composable with the InboxUIState from your ViewModel:

Copied to your clipboard
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.adobe.marketing.mobile.aepcomposeui.components.AepInbox
import com.adobe.marketing.mobile.aepcomposeui.style.InboxUIStyle
@Composable
fun InboxScreen(viewModel: InboxViewModel = viewModel()) {
// Collect the inbox state from ViewModel
val inboxUIState by viewModel.inboxUIState.collectAsStateWithLifecycle()
// Display the inbox with default styling
AepInbox(
uiState = inboxUIState,
inboxStyle = InboxUIStyle.Builder().build(),
observer = viewModel.observer
)
}

Display Inbox in View-based UI

To display an Inbox in a View-based (non-Compose) application, use ComposeView to embed the Compose UI within your Activity or Fragment:

Copied to your clipboard
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.adobe.marketing.mobile.aepcomposeui.components.AepInbox
import com.adobe.marketing.mobile.aepcomposeui.style.InboxUIStyle
class InboxActivity : AppCompatActivity() {
private val viewModel: InboxViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Create ComposeView programmatically or inflate from XML
val composeView = ComposeView(this).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
val inboxUIState = viewModel.inboxUIState.collectAsStateWithLifecycle().value
AepInbox(
uiState = inboxUIState,
inboxStyle = InboxUIStyle.Builder().build(),
observer = viewModel.observer
)
}
}
setContentView(composeView)
}
}

Refreshing Inbox Data

Inbox provides ways to refresh content cards:

Programmatic Refresh

You can programmatically refresh the Inbox using the refresh() method on the provider:

Copied to your clipboard
// In your ViewModel
fun refresh() {
// Fetch latest content from server
Messaging.updatePropositionsForSurfaces(listOf(surface))
// Update the inbox UI with new content
viewModelScope.launch {
inboxProvider.refresh()
}
}
// Call from your UI
Button(onClick = { viewModel.refresh() }) {
Text("Refresh Inbox")
}

This is useful for:

  • Refreshing on button taps
  • Auto-refreshing at intervals
  • Refreshing after specific app events

Automatic Refresh on Initial Load

The getInboxUI() method automatically calls refresh() when first collected, so you don't need to manually trigger the initial load. The flow will emit:

  1. InboxUIState.Loading - Immediately upon collection
  2. InboxUIState.Success or InboxUIState.Error - After the fetch completes

Best Practices

  1. Pre-fetch Content: As described in Fetch Inbox settings and Content Cards, call updatePropositionsForSurfaces early in your app lifecycle (e.g., Application class, splash screen, or after user login) to ensure content is cached and ready when the inbox is displayed.

  2. Use ViewModel: Always manage the MessagingInboxProvider in a ViewModel to properly handle lifecycle and configuration changes. Use SharingStarted.WhileSubscribed when converting the Flow to StateFlow to properly handle configuration changes:

Copied to your clipboard
val inboxUIState: StateFlow<InboxUIState> = inboxProvider.getInboxUI()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = InboxUIState.Loading
)
  1. Wrap in Material Theme: When displaying the inbox, wrap it in your app's Material Theme to ensure proper styling of UI components:
Copied to your clipboard
AppTheme {
AepInbox(
uiState = inboxUIState,
inboxStyle = InboxUIStyle.Builder().build(),
observer = viewModel.observer
)
}
  1. Use Lifecycle-aware Collection: Use collectAsStateWithLifecycle() instead of collectAsState() to automatically stop collecting when the UI is not visible, improving app performance and battery life:
Copied to your clipboard
val inboxUIState by viewModel.inboxUIState.collectAsStateWithLifecycle()
  1. Surface Naming: Use descriptive surface paths that match your Adobe Journey Optimizer campaign configuration. Ensure the same surface string is used consistently between updatePropositionsForSurfaces and MessagingInboxProvider:
Copied to your clipboard
// Use the same surface path in both places
val surface = Surface("inbox")
Messaging.updatePropositionsForSurfaces(listOf(surface))
val inboxProvider = MessagingInboxProvider(surface)
  1. Full Refresh Pattern: When implementing a manual refresh (e.g., pull-to-refresh or refresh button), call both updatePropositionsForSurfaces to fetch fresh content from the server and then refresh() to update the UI:
Copied to your clipboard
fun onRefreshClicked() {
// Fetch latest content from server
Messaging.updatePropositionsForSurfaces(listOf(surface))
// Update the inbox UI with new content
viewModelScope.launch {
inboxProvider.refresh()
}
}
  1. Reuse Provider: Keep the MessagingInboxProvider instance alive as long as the Inbox view is visible. The provider maintains state and efficiently updates when content changes.

  2. Handle Multiple Surfaces: If your app has multiple Inboxes (e.g., notifications, promotions), create separate providers with different surfaces:

Copied to your clipboard
// Notifications inbox
val notificationsProvider = MessagingInboxProvider(Surface("notifications"))
// Promotions inbox
val promotionsProvider = MessagingInboxProvider(Surface("promotions"))
  • Privacy
  • Terms of Use
  • Do not sell or share my personal information
  • AdChoices
Copyright © 2026 Adobe. All rights reserved.