In-app messaging
OpenCDP fetches eligible in-app messages from the Data Gateway using the current screen (Include / Exclude page rules). You can either:
- Recommended — auto-present
modal/bannervia the SDK entry point, or - Custom UI — listen to the message stream and render yourself (
inline/inbox_cardalways need a host layout slot).
Supported on Flutter, Android, iOS, and React Native (via native modules).
| Platform | Auto-present entry | Screen source | Custom escape hatch |
|---|---|---|---|
| Flutter | Wrap app with OpenCDPInAppHost | autoTrackScreens + navigatorObservers / named routes | messageStream listener |
| Android | enableInAppAutoPresent: true at init | Activity title / trackScreenView | addInAppListener |
| iOS | enableInAppAutoPresent: true at init | VC title / trackScreenView | addInAppListener |
Prerequisites
- CDP API key and gateway URL
enableInAppMessages: true- User identified via
identify()before sync - Campaign Include screens match the exact strings your SDK sends on
/sync?screen=
Recommended — auto-present (modal / banner)
Flutter
await OpenCDPSDK.initialize(
config: OpenCDPConfig(
cdpApiKey: 'YOUR_CDP_API_KEY',
enableInAppMessages: true,
enableInAppAutoPresent: true,
autoTrackScreens: true,
),
);
MaterialApp.router(
navigatorObservers: [
if (OpenCDPSDK.instance.screenTracker != null)
OpenCDPSDK.instance.screenTracker!,
],
builder: (context, child) => OpenCDPInAppHost(
child: child!,
// Optional: keep auto-present, use your own modal/banner design
modalBuilder: (context, message) => MyBrandedModal(message: message),
bannerBuilder: (context, message, {required onPrimaryCta, required onClose}) =>
MyBrandedBanner(message: message, onCta: onPrimaryCta, onClose: onClose),
),
);
// Place where inline should appear in a screen:
OpenCDPInAppInlineSlot(slotId: 'home_above_balance');
Your modalBuilder widget should Navigator.pop(context, ctaIdOrNull) so the host can track click/dismiss. Omit the builders to use the SDK defaults. Use OpenCDPInAppInlineSlot / OpenCDPInAppInboxSlot for layout-bound render types.
Android
val config = OpenCDPConfig(
cdpApiKey = "YOUR_CDP_API_KEY",
enableInAppMessages = true,
enableInAppAutoPresent = true,
autoTrackScreens = true,
)
OpenCDP.initialize(this, config)
No Activity wrap required — the SDK presents AlertDialog / banner overlay on the resumed Activity.
iOS
let config = OpenCDPConfig(
cdpApiKey: "YOUR_CDP_API_KEY",
enableInAppMessages: true,
enableInAppAutoPresent: true,
autoTrackScreens: true
)
OpenCDP.shared.initialize(config: config)
No view wrap required — the SDK presents from the key window / top view controller.
Then identify the user:
await OpenCDPSDK.instance.identify(
identifier: 'user_123',
properties: {'email': '[email protected]'},
);
Navigate to a screen whose name is in the campaign Include list — the modal/banner should appear there automatically. Exclude always wins.
Advanced — custom UI via listener
Register a listener and branch on render_type. Disable enableInAppAutoPresent if you fully own modal/banner UI (avoid double presentation).
Flutter:
OpenCDPSDK.instance.inApp?.messageStream.listen((message) async {
switch (message.renderType) {
case InAppRenderType.modal:
// Full-screen dialog
break;
case InAppRenderType.banner:
// Top overlay strip
break;
case InAppRenderType.inline:
case InAppRenderType.inboxCard:
// Embed in page or inbox list
break;
case InAppRenderType.unknown:
break;
}
await OpenCDPSDK.instance.inApp?.trackImpression(message);
});
Android (Kotlin):
OpenCDP.addInAppListener { message ->
when (message.renderType) {
InAppRenderType.MODAL -> { /* AlertDialog */ }
InAppRenderType.BANNER -> { /* top overlay */ }
InAppRenderType.INLINE, InAppRenderType.INBOX_CARD -> { /* host slot */ }
else -> {}
}
}
iOS (Swift):
OpenCDP.shared.addInAppListener { message in
switch message.renderType {
case .modal: break
case .banner: break
case .inline, .inboxCard: break
default: break
}
}
Screen targeting (Include / Exclude)
- SDK keeps a current screen (
setCurrentScreen/ auto-track). - Sync sends
?screen=…. - Backend filters deliveries with
page_rules(empty Include = all screens; Exclude wins). - Only eligible messages are emitted to the stream / auto-presenter.
Use stable path-like names (/profile/personal-details) that match the dashboard Include picker exactly (case-sensitive).