Skip to Content
Mobile SDKsMessage Center

Message Center

Message Center stores durable inbox entries independently from push delivery. Each published locale contains two immutable DivKit snapshots:

  • SUMMARY: compact list-row content;
  • DETAIL: the full message opened from the inbox.

Both snapshots come from the same template revision. The SDK marks an entry read only after its DETAIL surface becomes visible.

Choose a presentation mode

ModeWho owns navigation?Who renders message content?
Ready-madeEngageNative Engage DivKit UI
EmbeddedHost appNative Engage list/detail views
HeadlessHost appHost app from key and payload

All modes share the same native inbox store, unread count, mutations, rendering cache, and action registry.

Ready-made inbox

Engage.messageCenter.display() Engage.messageCenter.display(entryId = entry.id)

Use this when Engage may own the complete inbox and detail navigation flow.

Embedded Flutter UI

The embedded list/detail APIs and immutable SUMMARY/DETAIL rendering contract are implemented on develop and require the next Android, iOS, and Flutter releases. Current tagged SDKs retain their released ready-made/headless interfaces.

When the app owns its Navigator, Scaffold, and app bars, embed content-only widgets:

Navigator.of(context).push( MaterialPageRoute( builder: (_) => Scaffold( appBar: AppBar(title: const Text('Messages')), body: EngageMessageCenterList( onEntryTap: (entry) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => Scaffold( appBar: AppBar(title: const Text('Message')), body: EngageMessageCenterDetail(entryId: entry.id), ), ), ); }, ), ), ), );

The widgets contain no Scaffold, AppBar, or nested Navigator. They bridge the ambient Material 3 color roles to native controls; the published DivKit document owns its visual background, border, corners, clipping, and shadow.

Embedded Android and iOS views

val list = EngageMessageCenterListView( context, onEntryTap = { entry -> router.openMessage(entry.id) }, ) val detail = EngageMessageCenterDetailView( context, onUnavailable = router::closeMissingMessage, ).apply { display(entry.id) }

Close Android reusable views with their host lifecycle.

Headless inbox

private val inboxPager by lazy { Engage.messageCenter.inbox.pager( pageSize = 20, sortOrder = InboxSortOrder.NEWEST_FIRST, ) } lifecycleScope.launch { inboxPager.state.collect(::renderInbox) } lifecycleScope.launch { inboxPager.refresh() } fun onInboxEndReached() { val state = inboxPager.state.value if (state.hasMore && !state.isLoadingMore) { lifecycleScope.launch { inboxPager.loadNextPage() } } }

Pagination contract

Creating a pager restores its cached window immediately and starts a refresh when Message Center is enabled. pageSize must be between 1 and 100; it controls the size of each network page, not a hard limit on state.entries.

Use the pager state to drive infinite scrolling:

  • entries is the cumulative, deduplicated result window in the requested sort order;
  • hasMore means the server returned a next cursor;
  • isLoadingMore distinguishes pagination from a full isRefreshing refresh;
  • error exposes a classified failure while preserving the previously loaded entries.

Call loadNextPage() only when hasMore is true and isLoadingMore is false. The pager appends the next page and publishes a new state. Calling it after the last page is a no-op. Concurrent calls of the same kind are coalesced, and refresh/load commands are serialized so a cursor cannot be consumed twice.

refresh() starts again at the first page but reloads enough pages to preserve the size of the current visible window when possible. It does not unexpectedly collapse an inbox that already loaded several pages. Close the pager with its owning screen or lifecycle to release its observation and native resources.

Each pager owns an independent result window. unreadCount is shared and replaying:

Engage.messageCenter.inbox.unreadCount.collect(::updateBadge)

Mutations include mark read, mark unread, mark all read, and delete. The headless payload intentionally has no presentation wrapper; a custom client renders the template contract itself.

Locale and offline behavior

Published snapshots are resolved for locale and retained as inbox records. Cached metadata and documents support intermittent connectivity. If content cannot be rendered, embedded and ready-made UI expose an unavailable/fallback state rather than marking the message read prematurely.