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
| Mode | Who owns navigation? | Who renders message content? |
|---|---|---|
| Ready-made | Engage | Native Engage DivKit UI |
| Embedded | Host app | Native Engage list/detail views |
| Headless | Host app | Host 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/DETAILrendering contract are implemented ondevelopand 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:
entriesis the cumulative, deduplicated result window in the requested sort order;hasMoremeans the server returned a next cursor;isLoadingMoredistinguishes pagination from a fullisRefreshingrefresh;errorexposes 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.