Mobile Apps – technical documentation for Flutter

Requirements

  • Minimum Flutter sdk version – 3.19
  • Minimum Dart sdk – 3.3.0
  • Minimum Android sdk – 34
  • Minimum iOS target – iOS 16
  • App should have configured Firebase Cloud Messaging (link: https://firebase.google.com/docs/cloud-messaging/flutter/client) to handle push notifications
  • App should have configured requests for appropriate permissions

Installation

Add the following to your `pubspec.yaml` file:

dependencies:
    getresponsemobilesdk_flutter:
        git:
           url: https://github.com/GetResponse/MobileSDK-Flutter.git

Firebase

The SDK uses Firebase Cloud Messaging to handle push notifications from the GetResponse App. 

  1. Follow the steps in the official Firebase guide to add Firebase to your project. You only need the Cloud Messaging plugin.
    • Note: Initializing Firebase in your app is covered in the code snippets below in the Usage section. 
  2. Upload your APN keys to Firebase.

iOS

Configure Notification Service Extension target to show images for iOS notifications in the background. 

  1. Open the iOS project in Xcode.
  2. If you don’t have it already, add a Notification Service Extension target:
    • Go to File > New > Target.
    • Select Notification Service Extension, give it a name, and click Finish.
    • Click Cancel on the next dialog that asks to activate the scheme.

Configure required app capabilities by going to Signing & Capabilities > + Capability:

  1. In main target, add:
    • App Groups
      • Select an existing group or create one.
    • Push Notifications
    • Background Modes
      • Select Background fetch and Remote notifications
  2. In Notification Service Extension target, add:
    • App Groups
      • Select the same group as in the main target
    • Push Notifications

Add Firebase Messaging dependency to Notification Service Extension target in Podfile:

target 'NotificationServiceExtension' do
  use_frameworks!
  pod 'FirebaseMessaging'
end

Other

Integrate a solution of your choice to handle incoming Android and iOS notifications.

We recommend flutter_local_notifications and use it in the examples.

Usage

Configure SDK

  1. Go to app.getresponse.com > Web Push Notifications > Mobile apps to get the Application ID, Secret, and Entrypoint
  2. Add initialization of GetResponseSDK and Firebase Messaging in your main method.
void initGetResponse() {
  GetResponsePushNotificationService().configure(
      applicationId: /*applicationId*/,
      secret: /*secret*/,
      entrypoint: /*entrypoint*/,
  );
}
 
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  initGetResponse();
  runApp(const MyApp());
}

Managing consent

To start sending notifications, request system notifications permission and send user consent to GetResponse:

final notificationSettings = await FirebaseMessaging.instance.requestPermission();
if (notificationSettings.authorizationStatus != AuthorizationStatus.authorized) {
  // Handle notifications permission not granted
  return;
}
final apnsToken = await FirebaseMessaging.instance.getAPNSToken();
final requireApnsToken = Platform.isIOS;
if (requireApnsToken && apnsToken == null) {
  // Handle APNs token missing
  return;
}
final fcmToken = await FirebaseMessaging.instance.getToken();
if (fcmToken == null) {
  // Handle FCM token missing
  return;
}
await GetResponsePushNotificationService().consent(
  lang: /*language code*/,
  externalId: /*custom external id*/,
  email: /*email (optional)*/,
  fcmToken: fcmToken,
);

Send consent on every token refresh:

FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) async {
    await GetResponsePushNotificationService().consent(
        lang: /*language code*/,
        externalId: /*custom external id*/,
        email: /*email (optional)*/,
        fcmToken: fcmToken,
    ); 
}).onError((err) {
    /* Handle error */
});

Send consent on every token refresh:

await GetResponsePushNotificationService().removeConsent()

Handling notifications

Use the following method to handle notifications coming from GetResponse:

GetResponsePushNotificationService().handleIncomingNotification(Map<String, dynamic>, EventType);

This method parses the notification to a NotificationHandler object so that you can access the data easily and updates the notification statistics based on the passed EventType – clicked or showed. It should be used on many occasions that are presented below with the use of flutter_local_notifications package but you can use any solution of your choice for displaying local notifications. See a full example of a LocalNotificationService below. 

Note: Remember to configure the solution of your choice before proceeding (e.g. initialize flutter_local_notifications)

Notification displayed while app is in the background

Create a background handler as a global function outside of any class.
Important: This handler works in background isolate so GetResponseSDK and local notifications solution have to be initialized again.

@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // Initialize local notifications solution if needed
  // await LocalNotificationService().initialize();
  initGetResponse();
  // Notify GetResponse that the notification was delivered to the user
  final notificationHandler = await GetResponsePushNotificationService().handleIncomingNotification(message.data, EventType.showed);
  if (Platform.isAndroid) {
    // Show local notification for Android
    // await LocalNotificationService().displayPushMessage(notificationHandler, message);
  }
}

Add handler to firebase configuration in main method:

FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);

Update Notification Service Extension class in XCode for iOS:

import FirebaseMessaging
 
class NotificationService: UNNotificationServiceExtension {
    ...
 
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        if let bestAttemptContent = bestAttemptContent {
            Messaging.serviceExtension().populateNotificationContent(bestAttemptContent, withContentHandler: contentHandler)
        }
    }
    
    ...
}

Notification displayed while app is in the foreground

FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
    // Notify GetResponse that the notification was delivered to the user
    final notificationHandler = await GetResponsePushNotificationService().handleIncomingNotification(message.data, EventType.showed);
    // Show notification
    // LocalNotificationService().displayPushMessage(notificationHandler, message);
});

Notification tapped while app is in the background

  • If app is not terminated
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
    // Notify GetResponse that user has opened the notification
    final notificationHandler = await GetResponsePushNotificationService().handleIncomingNotification(message.data, EventType.clicked);
    // Custom operations e.g. navigate to page
});
  • If app is terminated
    Note: Put this code in a place so it runs on app open but after initalizing GetResponse and local notifications solutions (e.g. main method) 
FirebaseMessaging.instance.getInitialMessage().then((message) async {
    if (message != null) {
        // Notify GetResponse that user has opened the notification
        final notificationHandler = await GetResponsePushNotificationService().handleIncomingNotification(message.data, EventType.clicked);
        // Custom operations e.g. navigate to page
    }
});

Notification tapped while app is in the foreground

Note: We use flutter_local_notifications in this example, the solution of your choice may need adjusting

import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
 
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:getresponsemobilesdk_flutter/getresponsemobilesdk_flutter.dart';
import 'package:getresponsemobilesdk_flutter/notification_handler.dart';
import 'package:path_provider/path_provider.dart';
 
// Important! Keep this method outside of any class
@pragma('vm:entry-point')
Future<void> handleLocalNotificationAction(NotificationResponse notification) async {
  final payload = notification.payload;
  if (payload != null) {
    // Notify GetResponse that user has opened the notification
    final notificationHandler = await GetResponsePushNotificationService()
        .handleIncomingNotification(GetResponsePushNotificationService.convertStringDataToPayload(payload), EventType.clicked);
  }
}
 
class LocalNotificationService {
  LocalNotificationService._privateConstructor();
 
  static final LocalNotificationService _instance = LocalNotificationService._privateConstructor();
 
  factory LocalNotificationService() {
    return _instance;
  }
 
  final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
 
  // Run this in the main method
  Future<void> initialize() async {
    await _flutterLocalNotificationsPlugin.initialize(
      const InitializationSettings(
        android: AndroidInitializationSettings('@mipmap/ic_launcher'),
        iOS: DarwinInitializationSettings(
          requestSoundPermission: true,
          requestBadgePermission: false,
          requestAlertPermission: true,
        ),
      ),
      onDidReceiveNotificationResponse: handleLocalNotificationAction,
      onDidReceiveBackgroundNotificationResponse: handleLocalNotificationAction,
    );
  }
 
  Future<void> displayPushMessage(NotificationHandler? handler, RemoteMessage message) async {
    String imageUrl = message.notification?.apple?.imageUrl ?? message.data['image'] ?? '';
    final androidImage = await _parseImageForAndroid(imageUrl: imageUrl);
    final iosImage = await _parseImageForIos(imageUrl: imageUrl);
 
    await _flutterLocalNotificationsPlugin.show(
      message.messageId?.hashCode ?? 1,
      message.notification?.title ?? handler?.title ?? 'empty title',
      message.notification?.body ?? handler?.body ?? 'empty body',
      NotificationDetails(
        android: AndroidNotificationDetails(
          handler?.channelId ?? 'default',
          'channel name',
          largeIcon: androidImage?.image,
          styleInformation: androidImage?.style,
          silent: true,
        ),
        iOS: DarwinNotificationDetails(attachments: iosImage),
      ),
      payload: message.data.toString(),
    );
  }
 
  Future<({AndroidBitmap<Object> image, BigPictureStyleInformation style})?> _parseImageForAndroid({
    required String imageUrl,
  }) async {
    if (imageUrl.isEmpty) return null;
    final http.Response response = await http.get(Uri.parse(imageUrl));
    final image = ByteArrayAndroidBitmap.fromBase64String(
      base64Encode(response.bodyBytes),
    );
    final style = BigPictureStyleInformation(image, hideExpandedLargeIcon: true);
    return (image: image, style: style);
  }
 
  Future<List<DarwinNotificationAttachment>?> _parseImageForIos({required String imageUrl}) async {
    if (imageUrl.isEmpty) return null;
    final http.Response response = await http.get(Uri.parse(imageUrl));
    final dir = await getTemporaryDirectory();
    var filename = '${dir.path}/image.png';
    final file = File(filename);
    await file.writeAsBytes(response.bodyBytes);
    return [DarwinNotificationAttachment(filename)];
  }
}

Available notification data

The data from the notification is available in the NotificationHandler object

public struct NotificationHandler {
  final String? title;
  final String? body;
  final String? imageUrl;
  final ActionType action;
  final String? redirectionDestination;
  final String channelId;
  final Map<String, String> customData;
}
  • title – title of notification
  • body – message body of notification
  • imageURL – image url of notification (optional)
  • action – selected action on notification tap (see Handling actions)
  • redirectionDestination – URL or deeplink to open
  • channelId  – Android notification channel ID
  • customData  – map (key, value) of custom properties that can be configured in the notification settings in the GetResponse App

Handling actions

There are 3 types of available actions:

  • open application
  • open URL
  • open deeplink

You can access the URL or deeplink path through the NotificationHandler.redirectionDestination and use it to redirect the user manually after the user clicks the notification.

if (notificationHandler?.action == ActionType.openURL) {
    // Open URL
}
if (notificationHandler?.action == ActionType.deeplink) {
    // Handle deeplink
}