Skip to main content
Complete guide to implementing push notifications in Flutter with Zixflow using Firebase Cloud Messaging (FCM) and flutter_local_notifications for display and action buttons.
Track your campaigns: Call trackMetric(opened) and track action clicks yourself. Campaigns can be Native (OS-rendered) or Custom (app-rendered). Zixflow sends Zixflow-Delivery-ID, Zixflow-Delivery-Token, plus routing keys (deeplink_url, action_buttons, template_id). For delivery, opens, action-button details, wire format, and Journeys event names (Delivered / Opened / Clicked), see Push Notification Tracking. The reference implementation is lib/push_handlers.dart in sdk-examples/flutter.

Prerequisites

  1. Configure FCM in the Zixflow dashboard — add your Firebase / FCM credentials
  2. Firebase project — create a project and add both iOS and Android apps
  3. Identify users — call identify() before sending notifications so tokens attach to a profile
  4. Test on physical devices — iOS Simulator cannot receive remote pushes
A device cannot receive Zixflow pushes until you: (1) register a device token, (2) identify a person, (3) obtain notification permission.

Platform-Specific Requirements

iOS

  • iOS 13.0+, Xcode 14.0+
  • Push Notifications capability enabled
  • Optional Notification Service Extension for rich push and reliable delivered metrics
  • Physical device for testing

Android

  • API 21+ (Android 5.0)
  • google-services.json in android/app/
  • POST_NOTIFICATIONS for Android 13+ (API 33+)
  • coreLibraryDesugaring (required by flutter_local_notifications)

Step 1: Firebase Project Setup

  1. Create or open a project at Firebase Console
  2. Add an iOS app (bundle ID), download GoogleService-Info.plistios/Runner/
  3. Add an Android app whose package name matches applicationId in android/app/build.gradle, download google-services.jsonandroid/app/
applicationId must match the package name registered in Firebase, or FCM token registration fails.

Step 2: Add Dependencies


Step 3: Initialize Firebase and Zixflow

Native google-services.json / GoogleService-Info.plist is enough. FlutterFire (firebase_options.dart) is optional.

Step 4: Handle Push Notifications (Dart path)

Use FCM for receive/token and flutter_local_notifications to display notifications (including action buttons). Track opens with trackMetric and action presses with track('Push Notification Action Clicked', ...).

Token registration

Permission

Message listeners and tracking

Do not track opens with a custom event like push_notification_clicked. Use trackMetric(..., MetricEvent.opened) and the reserved action event name below.

Step 5: Identify Users

Tokens are linked to the profile when you call identify().

Local Notifications (Display + Action Buttons)

Channel ID zixflow_default, Android icon app_icon, iOS category ZX_2BTN, actions ACTION_0 / ACTION_1. Pass the full message.data map as JSON payload so taps can track and deep-link.
When showing the notification, set payload: jsonEncode(data) with the FCM data map so taps can track. Parse up to two AndroidNotificationActions (ACTION_0 / ACTION_1), download image_url / large_icon_url, map sticky to Android ongoing (with autoCancel: true), and set iOS categoryIdentifier: 'ZX_2BTN'. If template_id is present, route to a template-specific renderer first — see Template-Based Custom Rendering.

Background FCM handler

The background handler must also display the local notification (so action buttons work when the app is backgrounded or terminated):

Push Payload Fields

Zixflow always puts these tracking keys in data: Native: display content is in FCM notification / APNs aps. data carries tracking + routing keys (deeplink_url, action_buttons, template_id, notif_id, workspace_id). Full wire format: Push Notification Tracking. Custom: there is no notification block — the full content is in data. Official display/routing keys: Example Custom data:

iOS-Specific Setup

Push capabilities

  1. Open ios/Runner.xcworkspace
  2. Runner target → Signing & Capabilities
  3. Add Push Notifications
  4. Add Background Modes → enable Remote notifications

AppDelegate — register ZX_2BTN

Notification Service Extension (rich push + delivered)

  1. File → New → Target → Notification Service Extension (e.g. NotificationServiceExtension)
  2. Enable App Groups on Runner and the extension (same group ID, e.g. group.com.yourcompany.app)
  3. Podfile — NSE only (Runner does not need zixflow/fcm for this Dart path):
  1. NotificationService.swift:
Use cdpApiKey (not apiKey) and set .appGroupId(...) to the same App Group as the host app.

Android-Specific Setup

Gradle (plugins DSL)

android/settings.gradle:
android/app/build.gradle:
Place a white notification icon drawable named app_icon (referenced by AndroidInitializationSettings('app_icon')).
Zixflow sends deeplink_url for body taps and action_buttons[].deeplink for action taps. After tracking Opened / Action Clicked, open those URLs. The sample app (push_handlers.dart + navigation.dart) uses:
Attach navigatorKey to MaterialApp. Full open / action / delivered contract: Push Notification Tracking.

Sticky Notifications

data.sticky is an official Custom-mode field. Map it in your AndroidNotificationDetails. Native OS rendering does not honor sticky. Values: “Sticky” means the notification survives passive dismissal — the user swiping it away, or hitting “Clear all”. This is Android-only and only works on the Custom (app-rendered) path.
This requires two separate Android notification flags, not one — and a third piece of manual cleanup for action buttons.
  • autoCancel: true must always be on, regardless of sticky
  • ongoing: sticky is what actually blocks swipe and “Clear all” when sticky is "until_click" / "true" / "ongoing"
  • "until_swipe" / "no_clear" uses FLAG_NO_CLEAR without ongoing
  • Action buttons need an explicit cancel call in your action handler
Full details: Push Notification Tracking — Sticky Notifications.

Template-based custom rendering (template_id)

Every push sent from a dashboard template includes data.template_id. Map known IDs to a dedicated renderer (see push_templates.dart) and fall back to the generic field-driven builder. The sample app also checks template_type == "custom" as a local routing gate — the official payload field is template_id. Full pattern: Template-Based Custom Rendering.

Testing

  1. Register token + identify() a test user
  2. Enable LogLevel.debug
  3. Send a test push from the Zixflow dashboard (add your custom-data keys if testing buttons or deeplinks)
  4. Use a physical iOS device; Android emulator needs Google Play Services

Troubleshooting


Best Practices

  1. Initialize Firebase before Zixflow, then your push handlers
  2. Identify users after login; clearIdentify() on logout
  3. Store the full FCM data map in the local-notification payload for tracking
  4. If you add per-button links, prefer the button link over the body link on action taps
  5. Cancel the notification ID on action press (Android)
  6. Re-initialize Zixflow in the background notification-response isolate
  7. See Push Notification Tracking for metrics details