Push and in-app notifications SDK for Flutter

This SDK is for Flutter users wanting to send and manage push notifications and in-app notifications from Ortto.


Prerequisites

Please note the following prerequisites for Android and iOS platforms.

Android

  1. Use Gradle version 7.2.
  2. Minimum Android SDK API level: 26 or newer (see Android documentation).
  3. Minimum Kotlin version: 1.9.0.

shell

# root build.gradle buildscript { ext.kotlin_version = '1.7.10' }

5. Add Configuration to dashboard: /setup/push-notifications/platforms/android

6. Add Google Play services to your project.

shell

# root build.gradle buildscript { dependencies { classpath 'com.google.gms:google-services:4.4.0' } } # module build.gradle apply plugin: 'com.google.gms.google-services'

iOS

  1. Download Xcode.
  2. Configure the Deployment Target to 13.0 or newer.
  3. Have access to a physical device to receive notifications (for testing).
  4. Add configuration to: /setup/push-notifications/platforms/ios.

Project configuration

  1. Open Firebase.If you don't have a Firebase project yet, please set a new one up.
  2. Create a new Firebase Flutter app.
  3. Follow the instructions to install Firebase in your application:
    1. Add firebase to your project: flutter pub add firebase_core
    2. From any directory, run this command: dart pub global activate flutterfire_cli
    3. Then, at the root of your Flutter project directory, run this command: flutterfire configure --project=<PROJECT_ID>
      This automatically registers your per-platform apps with Firebase and adds a lib/firebase_options.dart configuration file to your Flutter project.
  4. Ensure Firebase is configured in your main application using the generated options class.
  5. Ensure WidgetsFlutterBinding.ensureInitialized() is called.


Installation

  1. Open your Flutter project using the command line.
  2. Add the Ortto Flutter SDK.

shell

# install ortto sdk $ flutter pub add ortto_flutter_sdk # Which will add this to your pubspec dependencies: ortto_flutter_sdk: ^1.0.0

Add Firebase messaging:

shell

flutter pub add firebase_messaging

3. Run pod install.

shell

pod install --repo-update --project-directory=ios # Pod installation complete! There are <number> dependencies from the Podfile and <number> total pods installed.

4. Ensure google-services-plugin is enabled in android/build.gradle.

5. Fetch the google-services.json from your Firebase project and copy it to android/app/google-services.json.


Initialize

  1. Import the Ortto Flutter SDK package.

shell

import 'package:ortto_flutter_sdk/ortto_flutter_sdk.dart';

2. Initialize the core Ortto SDK. 

NOTE: The elements Application key, Datasource ID, Capture JS URL and API Endpoint required for this step and step 3 (below) are found in your Ortto account's settings at Settings > In-app notifications > Android in-app notifications.

shell

await Ortto.instance.init( appKey: '<Application key>', endpoint: '<API endpoint>', );

3. Initialize the Ortto In-App notifications SDK.

shell

await Ortto.instance.initCapture( dataSourceKey: '<Datasource ID>', captureJsUrl: '<Capture JS URL>', apiHost: '<API endpoint>', );

Identifying contacts

A contact is considered anonymous when they are not logged in to the app or they cannot be identified using the identify function.

While push notifications can be sent to anonymous contacts, we recommend identifying your contacts so they can be linked to your existing CDP records. This then gives you the opportunity to personalize push notifications based on that contact’s data

The identify method identifies contacts who are subscribed to push and recognizes them when they log in to your app. Once logged in, the contact can receive notifications.

Identifying people is also necessary for tracking events.

The identify method can capture the following contact attributes:

  • firstName,
  • lastName,
  • contactId,
  • email,
  • externalId,
  • phone, and
  • acceptsGDPR.

IMPORTANT: You must identify contacts by a minimum of 1 of the following attributes:

  • email
  • phone
  • contactId, or
  • externalId.

Additional attributes are optional.

To identify contacts, create a UserID object:

shell

const uuid = Uuid(); final user = UserID( externalId: uuid.v4(), email: 'pete@example.com', ); await Ortto.instance.identify(user);

The UserID object saves the contact’s information on the device and enables tracked events to be associated with the contact.

NOTE: The contact’s device must be authorized before you can identify them (using identify()). Otherwise, Ortto’s API will not be able to handle the identify request.

The SDK can only identify one contact at a time, and will save the most recently identified contact. For example, if Jane Smith is initially identified, and then the identify function calls John Smith, the SDK will consider John Smith the contact using the app.


Track actions and events

Delivery events

Push message delivery (receipt) is automatically tracked in the Ortto SDK. Delivery events will be reflected in the Ortto app as a Delivered Push activity associated with a contact.

Click tracking

Within your deep link activity handler, call the Ortto instance trackLinkClick with the link that was inserted into the deep link activity.

This method will send a message to Ortto’s servers with the tracking information and return a LinkUtm object with campaign, medium, source and content attributes you can use for marketing data analysis.

The trackLinkClick accepts one argument with the link string passed to your app from the deep linking mechanism you decide to use.

shell

await Ortto.instance.trackLinkClick(link);

This method will populate the Clicked push activity for a contact, including the deep link URL in the activity attributes.

Register push notification token

Fetch the current Firebase token (generated when you created the Firebase project) and pass it onto the Ortto identity API using the following method:

shell

final fcmToken = await FirebaseMessaging.instance.getToken(); if (fcmToken != null) { Ortto.instance.registerDeviceToken(fcmToken); }

This method should be called in an activity where you have identified the contact using identify() so Ortto can register which contact the push notification token is attached to.

Please note that:

  • dispatchPushRequest() ONLY works after registerDeviceToken() is called
  • registerDeviceToken() happens automatically for Android but needs to be called manually for iOS

Notification handling

To handle notification messages while your application is in the foreground, listen to the onMessage stream.

The stream contains a RemoteMessage, detailing various information about the payload, such as where it was from, the unique ID, sent time, whether it contained a notification and more. Since the message was retrieved while your application is in the foreground, you can directly access your Flutter application's state and context.

shell

FirebaseMessaging.onMessage.listen((RemoteMessage message) { Ortto.instance .onbackgroundMessageReceived(message.toMap()) .then((handled) { print("handled $handled"); return handled; }); });

Foreground and notification messages

Notification messages which arrive while the application is in the foreground will not display a visible notification by default, on both Android and iOS. It is, however, possible to override this behavior:

  • On Android, you must create a "High Priority" notification channel.
  • On iOS, you can update the presentation options for the application.

Background notifications

Handle background notification messages by registering a onBackgroundMessage handler. When messages are received, an isolate is spawned (Android only, iOS/macOS does not require a separate isolate) allowing you to handle messages even when your application is not running.

There are a few things to keep in mind about your background message handler:

  1. It must not be an anonymous function.
  2. It must be a top-level function (e.g. not a class method which requires initialization).
  3. When using Flutter version 3.3.0 or higher, the message handler must be annotated with @pragma('vm:entry-point') right above the function declaration (otherwise it may be removed during tree shaking for release mode).

shell

# FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); # @pragma('vm:entry-point') Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); Ortto.instance .onbackgroundMessageReceived(message.toMap()) .then((handled) { print("handled $handled"); return handled; }); }

Showing an in-app notification

When you have created a popup-type capture widget in Ortto (and it is switched ON), you can choose to manually show or queue the widget.

You will need the widgetId, which you can find in the widget URL in Ortto. For example:

To manually show an in-app notifications widget, call showWidget on the Ortto instance.

shell

ElevatedButton( onPressed: () async { Ortto.instance.showWidget('<WIDGET_ID>'); }, child: const Text('Show Widget'), ),

Trigger in-app notifications from push notifications

In-app notifications can be triggered from push notifications, when configured correctly within Ortto and via the SDK.

To trigger a capture widget from a push notification, in Ortto you need to:

  1. Create a popup-type capture widget.
  2. Create a push notification.
  3. In the push notification, set the primary action to Show widget and select the relevant popup widget.

For more detailed setup instructions, read In-app notifications, under Creating a new capture widget.

When both the push notification and popup widget are switched ON, they will be linked so that when a user clicks on the push notification, it will trigger the popup widget to show.


Troubleshooting

Ensure that this code is added to your application entry-point where required

javascript

// Main entrypoint void main() async { WidgetsFlutterBinding.ensureInitialized(); // Initialize Firebase first await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); // Set up Ortto SDKs await Ortto.instance.init( appKey: '<KEY>', endpoint: '<ENDPOINT>' ); await Ortto.instance.initCapture( dataSourceKey: '<DATA_SOURCE_KEY>', captureJsUrl: '<CAPTURE_URL>', apiHost: '<CAPTURE_API_HOST>', ); // Identify User const uuid = Uuid(); final user = UserID( externalId: uuid.v4(), email: 'pete@example.com', ); await Ortto.instance.identify(user); // Enable foreground notifications for iOS // https://firebase.flutter.dev/docs/messaging/notifications/#foreground-notifications FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(badge: true, alert: true, sound: true); // Listen to foreground notifications // https://firebase.google.com/docs/cloud-messaging/flutter/receive#foreground_messages FirebaseMessaging.onMessage.listen((RemoteMessage message) { if (message.notification != null) { Ortto.instance.onbackgroundMessageReceived(message.toMap()); .then((handled) { print("handled $handled"); return handled; }); } }); // Listen to background notifications // https://firebase.google.com/docs/cloud-messaging/flutter/receive#apple_platforms_and_android FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); } @pragma('vm:entry-point') Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { // If you're going to use other Firebase services in the background, such as Firestore, // make sure you call `initializeApp` before using other Firebase services. await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); // Pass the message to Ortto Ortto.instance .onbackgroundMessageReceived(message.toMap()) .then((handled) { print("handled $handled"); return handled; }); }

For iOS

Ensure you have:

  • Installed pods pod install --repo-update --project-directory=ios
  • Enabled notifications and background processing entitlements
  • Added a UNNotificationServiceExtension target

swift

import UserNotifications import OrttoSDKCore import OrttoPushMessagingFCM class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler // 1. Pass notification to Ortto to attempt to handle let handled = PushMessaging.shared.didReceive(request, withContentHandler: contentHandler) // 2. If handled === false, Ortto has ignored the notificatio and it requires processing if !handled { bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent { // Modify the notification content here... bestAttemptContent.title = "\(bestAttemptContent.title) [🙏]" contentHandler(bestAttemptContent) } } } override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { contentHandler(bestAttemptContent) } } }