# 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](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 (Push notification module)
-----------------------------------

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](https://firebase.google.com/docs/flutter/setup). 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](https://firebase.google.com/docs/cloud-messaging/ios/client#upload_your_apns_authentication_key) to Firebase.

iOS (Push notification module)
------------------------------

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](https://pub.dev/packages/flutter_local_notifications) and use it in the examples.

Usage
-----

### Configure SDK

1.  Go to [app.getresponse.com](http://app.getresponse.com/) > Web Push Notifications > Mobile apps to get the Application ID, Secret, and Entrypoint  
    ![](https://confluence.int.getresponse.com/download/attachments/245976153/image-2024-3-20_14-31-15.png?version=1&modificationDate=1715245333370&api=v2)
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 */
    });

To remove consent:

    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](https://pub.dev/packages/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](https://pub.dev/packages/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);
    }

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](https://pub.dev/packages/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

![](https://confluence.int.getresponse.com/download/attachments/245976153/image-2024-3-20_14-31-41.png?version=1&modificationDate=1715254973308&api=v2)

### 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
    }