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.
- 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.
- Upload your APN keys to Firebase.
iOS
Configure Notification Service Extension target to show images for iOS notifications in the background.
- Open the iOS project in Xcode.
- 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:
- In main target, add:
- App Groups
- Select an existing group or create one.
- Push Notifications
- Background Modes
- Select Background fetch and Remote notifications
- App Groups
- In Notification Service Extension target, add:
- App Groups
- Select the same group as in the main target
- Push Notifications
- App Groups
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
- Go to app.getresponse.com > Web Push Notifications > Mobile apps to get the Application ID, Secret, and Entrypoint
- 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);
}
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 notificationbody
– message body of notificationimageURL
– image url of notification (optional)action
– selected action on notification tap (see Handling actions)redirectionDestination
– URL or deeplink to openchannelId
– Android notification channel IDcustomData
– 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
}