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
- Install Android Studio: https://developer.android.com/studio/install.
- Use Gradle version 7.2.
- Minimum Android SDK API level: 26 or newer (see Android documentation).
- 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
Download Xcode. Configure the Deployment Target to 13.0 or newer. Have access to a physical device to receive notifications (for testing). Add configuration to:/setup/push-notifications/platforms/ios
.Project configuration
Open Firebase.If you don't have a Firebase project yet, please set a new one up. Create a new Firebase Flutter app. Follow the instructions to install Firebase in your application:
Add firebase to your project:flutter pub add firebase_core
From any directory, run this command:dart pub global activate flutterfire_cli
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 alib/firebase_options.dart
configuration file to your Flutter project. Ensure Firebase is configured in your main application using the generatedoptions
class. EnsureWidgetsFlutterBinding.ensureInitialized()
is called.dart
void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); runApp(const MyApp()); }
Installation
Open your Flutter project using the command line. 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 inandroid/build.gradle
.5. Fetch the
google-services.json
from your Firebase project and copy it toandroid/app/google-services.json
.Initialize
Import the Ortto Flutter SDK package.dart
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.
dart
await Ortto.instance.init( appKey: '<Application key>', endpoint: '<API endpoint>', );
3. Initialize the Ortto In-App notifications SDK.
dart
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:
phone contactId, or externalId.Additional attributes are optional.
To identify contacts, create a
UserID
object:dart
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. To clear the currently identified contact, call
clearIdentity()
as shown in the example below:dart
Ortto.instance.clearIdentity().then((IdentityResult value) { print("Ortto identity clear success!"); }).catchError((error) { print(error); });
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.dart
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:
dart
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 afterregisterDeviceToken()
is calledregisterDeviceToken()
happens automatically for Android but needs to be called manually for iOSNotification 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.dart
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:
It must not be an anonymous function. It must be a top-level function (e.g. not a class method which requires initialization). 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).dart
# 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.dart
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:
Create a popup-type capture widget. Create a push notification. 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
dart
// 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 podspod install --repo-update --project-directory=ios
Enabled notifications and background processing entitlements Added aUNNotificationServiceExtension
targetswift
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) } } }
NotificationService error
If you see an error relating to the Flutter build step when trying to install the Capture code for iOS, like this:
'shared' is unavailable in application extensions for iOS: Use view controller based solutions where appropriate instead.
you need to remove the "install all packages" section in the NotificationExtension target:
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
This section is added by default by Flutter, but Xcode doesn't accept adding UI code to a background job. This behaviour was introduced in Xcode 13.1