flutter doctor passing for Android and iOS.google-services.json.agconnect-services.json and HMS configuration.In your app’s pubspec.yaml:
Then run:
To iterate on the plugin locally:
The native Dengage SDK (Android and iOS) reads backend URLs from configuration. Your backend team will provide the actual URL values for each environment (e.g. dev, staging, production). You must add these keys in your app and set the value for each key to the URL given by the backend team.
AndroidManifest.xml → <meta-data>)Use these as android:name inside <application> under a <meta-data> tag. The android:value is the URL from the backend team.
| Key | Purpose |
|---|---|
den_event_api_url | Event ingestion endpoint. All events (page view, cart, order, custom events) are sent here. |
den_push_api_url | Push notification API. Used for token registration and push delivery. |
den_device_id_api_url | Device identity endpoint. Used for contact/device registration and sync. |
den_in_app_api_url | In-app message API. Used to fetch and display in-app campaigns. |
den_geofence_api_url | Geofence API. Used when geofence module is enabled for location-based campaigns. |
fetch_real_time_in_app_api_url | Real-time in-app API. Used when you call showRealTimeInApp to fetch an in-app message for the current screen. |
Example (value is placeholder — get real value from backend):
Runner/Info.plist)Use these as the plist key; the value is a string (the URL from the backend team).
| Key | Purpose |
|---|---|
DengageEventApiUrl | Event ingestion endpoint. |
DengageDeviceIdApiUrl | Device identity endpoint. |
DengageApiUrl | Push / main API base URL. |
DengageGeofenceApiUrl | Geofence API URL (when using geofence). |
fetchRealTimeINAPPURL | Real-time in-app API URL. |
Example (value is placeholder — get real value from backend):
<meta-data android:name="KEY" android:value="URL_FROM_BACKEND" /> in AndroidManifest.xml (see Section 4).Runner/Info.plist (see Section 5).build.gradle (application, not the plugin)Path: android/app/build.gradle.
google-services.json:android/app/build.gradle:android/build.gradle (or settings.gradle plugin block) must have the Google Services classpath; with the new plugin DSL it is often in settings.gradle:build.gradle or settings.gradle):AndroidManifest.xmlPath: android/app/src/main/AndroidManifest.xml.
Use your own package for android:name (e.g. com.yourcompany.yourapp.push.PushNotificationReceiver) and implement the class as in Section 6.
<meta-data> per key. The value for each key must be provided by your backend team (see Section 3.1 for the list of keys).MainActivityInitialize Dengage before the Flutter engine runs. Use DengageCoordinator from the plugin:
sdk-hms is included and HMS is configured.android/build.gradle can add sdk-geofence via a property; ensure your app has location permissions and endpoint meta-data.google-services.jsonPut your Firebase google-services.json in android/app/. Do not commit keys; use env or CI secrets for production.
Path: ios/Podfile.
Runner target, before flutter_install_all_ios_pods:DengageNotificationServiceExtension: required for rich notifications (image/media and carousel). Create this target in Xcode (File → New → Target → Notification Service Extension), name it DengageNotificationServiceExtension, and add the Dengage dependency to it (or rely on search_paths from Runner). The extension’s only job is to call Dengage.didReceiveNotificationRequest(_:withContentHandler:) (see Section 6).
DengageContentExtension: required for carousel UI on iOS. Create a Notification Content Extension target (e.g. DengageContentExtension), set its NSExtensionMainStoryboard to the storyboard that uses your carousel view controller (e.g. DengageNotificationViewController), and set UNNotificationExtensionCategory to the category ID you use for carousel (e.g. DENGAGE_CAROUSEL_CATEGORY). See Section 6.
Then:
Runner/Info.plist add the endpoint keys listed in Section 3.2. The string value for each key must be provided by your backend team.NSLocationWhenInUseUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription). These are required by iOS when the app or the Dengage geofence module uses location.In ios/Runner/AppDelegate.swift (or .m if using Objective-C):
YOUR_IOS_INTEGRATION_KEY with the key from the Dengage dashboard.DengageCoordinator calls Dengage.setIntegrationKey, Dengage.initWithLaunchOptions, and forwards token and notification response to the native SDK.This section describes how the Flutter SDK supports rich push (notifications with image/media) and carousel push (notifications with multiple swipeable items). Both require native code on Android and iOS; the Flutter example app in this repo includes full implementations.
FcmMessagingService, and the SDK handles the display.NotificationReceiver, override onCarouselRender, and build a custom notification with RemoteViews (collapsed + expanded layout). On iOS, you add a Notification Content Extension that reads the carousel payload from userInfo, displays a UICollectionView (or similar), and handles tap and optional NEXT/PREVIOUS actions.Why this is needed: When the backend sends a carousel push, the payload contains multiple items (each with image URL, title, description, target URL). The Dengage Android SDK does not render the carousel UI for you by default; it invokes your app’s NotificationReceiver with the carousel data. You must provide the UI (collapsed + expanded) and handle left/right navigation and item click.
Receiver class
Create a class that extends com.dengage.sdk.push.NotificationReceiver and override onCarouselRender. This method is called when a carousel notification is received. Register this class in AndroidManifest.xml as in Section 4.2, and include the action com.dengage.push.intent.CAROUSEL_ITEM_CLICK so item taps are delivered to your receiver.
Layouts
You need XML layouts for the notification:
den_carousel_collapsed.xml.den_carousel_portrait.xml.den_carousel_landscape.xml.All of these use standard Android views (TextView, ImageView, etc.) and are inflated into RemoteViews, because notification content is rendered in the system UI process.
Implementation pattern
Inside onCarouselRender, the SDK passes you:
Message: the main notification (title, body, etc.)leftCarouselItem, currentCarouselItem, rightCarouselItem: three adjacent items from the carousel (type com.dengage.sdk.domain.push.model.CarouselItem). Each has image URL, title, description, target URL.The base class NotificationReceiver provides helper methods that return PendingIntents for: item click, left arrow (previous), right arrow (next), content click, and delete. You create RemoteViews for the collapsed and expanded layouts, set the title/message and item title/description text, and assign the pending intents to the arrow buttons and the current item area. You then load the images asynchronously into the RemoteViews image views (the Dengage SDK provides a helper like loadCarouselImageToView that loads from URL and updates the RemoteViews). Finally you build the Notification with setCustomContentView(collapsedView) and setCustomBigContentView(carouselView), and display it with NotificationManagerCompat.notify(message.messageId, notification) so the same notification is updated when the user swipes.
Example structure (package and layout names from the Flutter example):
android/app/src/main/res/layout/den_carousel_collapsed.xmlandroid/app/src/main/res/layout/den_carousel_portrait.xmlandroid/app/src/main/res/layout/den_carousel_landscape.xml (optional)android/app/src/main/kotlin/.../push/PushNotificationReceiver.ktWhy this is needed: On iOS, when a push payload contains a URL for an image or media, the system does not automatically download and attach it. You must add a Notification Service Extension. The extension runs in a separate process when a push arrives; it receives the UNNotificationRequest, can modify the UNMutableNotificationContent (e.g. download the image and add it as an attachment), and then call the content handler with the modified content. The Dengage iOS SDK provides an API that does this: it reads the payload, downloads the media, and attaches it to the content. You must call that API from your extension.
DengageNotificationServiceExtension.NotificationService.swift, implement didReceive(_:withContentHandler:). Inside it, get a mutable copy of the request’s content, then call Dengage so it can modify the content (download and attach media). After that, call the content handler with the (possibly modified) content so iOS displays the notification.DengageNotificationServiceExtension) with inherit! :search_paths so it uses the same pods as Runner (including Dengage). Run pod install so the extension can import and call Dengage.Why this is needed: For carousel notifications on iOS, the payload contains an array of items (each with image URL, title, description, link). The default notification UI only shows title and body. To show a swipeable list of items, you add a Notification Content Extension. The extension gets the notification payload, parses the carousel items, and presents a custom UI (e.g. a horizontal collection view). The category of the notification (set by the backend) must match the extension’s UNNotificationExtensionCategory so iOS uses your extension for that category.
In Xcode, add a new target: File → New → Target → Notification Content Extension. Name it e.g. DengageContentExtension.
Info.plist for the extension:
NSExtension → NSExtensionPointIdentifier: must be com.apple.usernotifications.content-extension.NSExtensionMainStoryboard: the storyboard that contains your carousel view controller (e.g. MainInterface).NSExtensionAttributes, set UNNotificationExtensionCategory to the category identifier that the backend sends for carousel notifications (e.g. DENGAGE_CAROUSEL_CATEGORY). This must match exactly so iOS routes carousel pushes to this extension.UNNotificationExtensionDefaultContentHidden: set to true if your custom UI replaces the default title/body; otherwise the system shows both.View controller
Your view controller must conform to UNNotificationContentExtension. In didReceive(_ notification: UNNotification):
notification.request.content.userInfo. The carousel items are typically under a key like carouselContent — an array of dictionaries.mediaUrl, title, desc (or description). Map these to a simple model (e.g. a struct or class with image, title, description).UICollectionView) so each cell shows one carousel item.Cell
Use a custom UICollectionViewCell (e.g. with a XIB: image view, title label, description label). In cellForItemAt, set the labels and load the image from the item’s mediaUrl asynchronously (e.g. URLSession.dataTask), then set the image on the image view on the main queue.
Actions
If the backend defines notification actions (e.g. “Next”, “Previous”), implement didReceive(_ response: UNNotificationResponse, completionHandler:). For “Next”/“Previous”, scroll the collection view to the next/previous item and call completionHandler(.doNotDismiss). When the user taps the notification itself or an action that should open the app/link, call completionHandler(.dismissAndForwardAction).
Storyboard
In the extension’s main storyboard, set the root view controller to your carousel view controller and connect the collection view outlet so it loads correctly when the extension runs.
A full reference implementation is in this repo under example/ios/DengageContentExtension/: DengageNotificationViewController.swift, CarouselNotificationCell.swift, DengageRecievedMessage.swift, MainInterface.storyboard, and Info.plist.
Call these after the app (and native SDK) are ready. Prefer calling from a State after initState or after WidgetsBinding.instance.addPostFrameCallback, so that native init has run.
| Method | Description |
|---|---|
DengageFlutter.setContactKey(String? contactKey) | Bind device to your user/customer ID. |
DengageFlutter.getContactKey() | Returns current contact key. |
DengageFlutter.setIntegerationKey(String key) | iOS only: set integration key from Dart. |
DengageFlutter.setFirebaseIntegrationKey(String key) | Android only: set Firebase integration key. |
DengageFlutter.setHuaweiIntegrationKey(String key) | Android only: set Huawei integration key. |
DengageFlutter.setPartnerDeviceId(String adid) | Sync partner device ID (e.g. advertising ID). |
DengageFlutter.setLogStatus(bool isVisible) | Enable/disable SDK logging. |
| Method | Description |
|---|---|
DengageFlutter.getToken() | Get FCM/APNs token. |
DengageFlutter.setToken(String token) | Manually set token. |
DengageFlutter.promptForPushNotifications() | iOS: show system permission dialog. |
DengageFlutter.promptForPushNotificationsWithPromise() | Returns bool? (granted/denied on iOS). |
DengageFlutter.setUserPermission(bool hasPermission) | Set opt-in state. |
DengageFlutter.getUserPermission() | Android: get current permission. |
DengageFlutter.registerForRemoteNotifications(bool enabled) | iOS only. |
DengageFlutter.getLastPushPayload() | Last received push payload. |
| Method | Description |
|---|---|
DengageFlutter.pageView(Object data) | Page view event (e.g. { "screenName": "home" }). |
DengageFlutter.addToCart(Object data) | Add to cart. |
DengageFlutter.removeFromCart(Object data) | Remove from cart. |
DengageFlutter.viewCart(Object data) | View cart. |
DengageFlutter.beginCheckout(Object data) | Begin checkout. |
DengageFlutter.placeOrder(Object data) | Place order. |
DengageFlutter.cancelOrder(Object data) | Cancel order. |
DengageFlutter.addToWishList(Object data) | Add to wishlist. |
DengageFlutter.removeFromWishList(Object data) | Remove from wishlist. |
DengageFlutter.search(Object data) | Search event. |
DengageFlutter.sendDeviceEvent(String tableName, Object data) | Custom event to a Dengage table. |
DengageFlutter.setTags(List<Object> tags) | Attach tags to the device. |
| Method | Description |
|---|---|
DengageFlutter.setNavigation() | Notify current screen (default). |
DengageFlutter.setNavigationWithName(String screenName) | Set screen for targeting. |
DengageFlutter.setCity(String city) | Set city context. |
DengageFlutter.setState(String state) | Set state context. |
DengageFlutter.setCartAmount(String amount) | Cart total for RTS. |
DengageFlutter.setCartItemCount(String count) | Cart item count for RTS. |
DengageFlutter.setCategoryPath(String path) | Category path. |
DengageFlutter.showRealTimeInApp(String screenName, Object data) | Request real-time in-app for the screen. |
| Method | Description |
|---|---|
DengageFlutter.getInboxMessages(int offset, int limit) | Paginated inbox messages. |
DengageFlutter.deleteInboxMessage(String id) | Delete one message. |
DengageFlutter.setInboxMessageAsClicked(String id) | Mark as clicked/read. |
| Method | Description |
|---|---|
DengageFlutter.setInAppLinkConfiguration(String deepLink) | Override in-app link handling. |
DengageFlutter.requestLocationPermissions() | Request location (e.g. for geofence). |
DengageFlutter.startGeofence() | Start geofence. |
DengageFlutter.stopGeofence() | Stop geofence. |
| Method | Description |
|---|---|
DengageFlutter.getSubscription() | Raw subscription metadata (integration key, tokens, device IDs). |
Use the InAppInline widget where you want a native inline in-app block (e.g. banner or slot). It embeds the native view via platform view.
setNavigationWithName(screenName) before showing the widget so targeting matches.SizedBox(height: 244, child: InAppInline(...))).Use the AppStoryView widget to show the native stories list. The widget embeds the native Dengage story view (a horizontal list of story items that the user can tap and swipe through). The content and targeting are configured in the Dengage dashboard (Content > Marketing > In-App, Story template); you only need to pass the same propertyId and screenName that you configured there.
getInboxMessages(offset, limit) to fetch a page of messages (each has id, title, message, receiveDate, isClicked, etc.). Use deleteInboxMessage(id) to remove one message and setInboxMessageAsClicked(id) to mark it as read/clicked. You can build a simple inbox screen that lists these and handles delete/click.setInAppLinkConfiguration(deepLink) to your app’s URL scheme or universal link host. The plugin also exposes an event channel (e.g. com.dengage.flutter/inAppLinkRetrieval) so you can listen in Dart for link callbacks when the user taps a link — check the plugin source for the exact channel name and payload format.com.dengage.flutter/onNotificationClicked). The plugin streams the payload or notification data when a notification is opened; see the plugin’s iOS/Android implementation for the exact event name and data shape.Replace integration keys in:
example/android/app/src/main/kotlin/.../MainActivity.kt (Firebase key, optional Huawei key).example/ios/Runner/AppDelegate.swift (iOS integration key).| Issue | Check |
|---|---|
| Push not received (Android) | google-services.json in android/app/, FCM service in manifest, Firebase key in Dengage dashboard and in setupDengage. |
| Push not received (iOS) | Capabilities: Push Notifications + Remote notifications; APNs key/cert in Dengage; registerForPushToken in AppDelegate; correct integration key. |
| In-app not showing | setNavigationWithName(screenName) called; screen name matches campaign targeting; endpoint meta-data/Info.plist correct. |
| Carousel not showing (Android) | Receiver registered with CAROUSEL_ITEM_CLICK; onCarouselRender implemented; layouts and notification channel created. |
| Rich/carousel not showing (iOS) | Service Extension calls Dengage.didReceiveNotificationRequest; Content Extension category matches payload; storyboard and view controller wired. |
| Build errors (iOS) | pod install; minimum iOS version in Podfile; Dengage pod version matches what the plugin expects. |
| Build errors (Android) | Repositories (JitPack, Google); Dengage SDK version; minSdk 21; Java 17 if required. |