Push Notifications allow you to reach users who have your application in the foreground, background and terminated, including when your application is not connected to Ably. Push notifications allow you to run code and show alerts to the user. On iOS, Ably connects to APNs to send messages to devices. On Android, Ably connects to Firebase Cloud Messaging to send messages to devices. As both services do not guarantee message delivery and may even throttle messages to specific devices based on battery level, message frequency, and other criteria, messages may arrive much later than sent or ignored.
If you are using the official Firebase messaging library package along with this library, then you must add the following block to your Android application's manifest file, within the application
element. This is a workaround that prevents a conflict that happens when the two libraries are installed together.
<receiver android:name="io.ably.flutter.plugin.push.FirebaseMessagingReceiver"
tools:node="remove">
</receiver>
See issue #226.
- Activating devices from your server: Device activation is automatic in ably-flutter, just call
Push#activate
. However, Ably-java and ably-cocoa allow you to implement delegate methods to activate devices manually on your server instead of automatically. - Push Admin API: The Push APIs in this SDK are limited to managing the push notification features related to current device. The Push Admin API allows you to manage device registrations and subscriptions related to other devices. This is functionality designed for servers.
- Push Registrations cannot be updated on Android: fails with
No authentication information provided
. If you listen toactivationEvents.onUpdateFailed
, you should get an error:No authentication information provided
. This is a problem you would commonly face during development, since FCM may change the device token when the app is reinstalled. However, this is less of a problem in production.
- Android API level 19+ (Android 4.4+)
- Android devices
- Android emulator (with Google APIs)
- iOS 10+
- Physical devices only
- Not supported: iOS Simulator. Calling
UIApplication:registerForRemoteNotifications
will result inapplication:didFailToRegisterForRemoteNotificationsWithError
method being called in your AppDelegate with an error:remote notifications are not supported in the simulator
). This is an iOS simulator limitation.
To get push notifications setup in your own app, read Setting up your own app.
- Update the application ID (
applicationId "io.ably.flutter.example"
) in the example application inexample/android/app/build.gradle
to a unique application ID. - Create a Firebase project, and in the Project settings, add an android app with your unique application ID. Follow the steps provided on the setup process, or the following:
- You can leave
Debug signing certificate SHA-1
empty. - Download the generated
google-services.json
file - Replace the default
google-services.json
inexample/android/app/
with the one generated for your project
- You can leave
- Provide ably with the FCM server key: In your firebase project settings, create or use an existing cloud messaging server key, and enter it in your Ably app's dashboard (App > Notifications tab > Push Notifications Setup > Setup Push Notifications).
- You need to have a Apple developer program membership ($99/year)
- Open your iOS app in Xcode: when in your project directory, run
xed ios
or double clickios/Runner.xcworkspace
inyour_project_name/ios
- Add your developer account in Xcode, in
Preferences
>Accounts
. - In the project navigator, click
Runner
> ClickRunner
target (not project) >General
. Change the bundle identifier to a unique identifier. Then, under theSigning & Capabilities
tab >Team
dropdown menu, select your developer team associated with your developer account. This will register your bundle ID on App Store connect if it is not yet registered. - Create a push notification certificate (
.p12
) and upload it to the Ably dashboard to allow Ably to authenticate with APNs on your behalf, using How do I obtain the APNs certificates needed for iOS Push Notifications?. - Add
Push Notifications
capability: Click Runner in project navigator, clickRunner
target, under the Signing & Capabilities tab, click+ Capability
, and selectPush Notifications
. - Add
remote notification
background mode:- Under the Signing & Capabilities tab, click
+ Capability
and selectBackground Modes
. - Check
remote notifications
.
- Under the Signing & Capabilities tab, click
- Add your developer account in Xcode, in
- Create a Firebase project if you do not have one
- Tip: Follow the steps below with Android Studio opened with the Android project, instead of the flutter project.
- Set up Firebase in your Android app, following only the Set up the SDK and Edit your app manifest steps from the Firebase guide. The guide will instruct you to create a firebase project, add the firebase configuration file (
google-services.json
), add thegoogle-services
to the classpath (classpath
), and apply the plugin (apply plugin:
) theapp
module. - In your
app/build.gradle
, ensure that yourminSdkVersion
is set to 19 or above. - Add your android app to the firebase project
- Provide ably with the FCM server key: In your firebase project settings, create or use an existing cloud messaging server key, and enter it in your Ably app's dashboard (App > Notifications tab > Push Notifications Setup > Setup Push Notifications).
- Handle messages received on your device by extending
FirebaseMessagingService
. An example is shown in the example app,PushMessagingService
. This is the service class you specify in theAndroidManifest.xml
. For more information, have a look at the receiving messages section.
- You need to have a Apple developer program membership ($99/year)
- Open your iOS app in Xcode: when in your project directory, run
xed ios
or double clickios/Runner.xcworkspace
inyour_project_name/ios
- Ensure your application bundle ID is registered on App Store connect. This is done automatically by Xcode when you select your team under
Signing & Capabilities
. - Create a
.p12
certificate and upload it to the Ably dashboard to allow Ably to authenticate with APNs on behalf of you, using How do I obtain the APNs certificates needed for iOS Push Notifications?.- When running your application via Xcode or your machine (Android Studio, command line), your application runs in either debug, profile or release mode. In all cases, your application will use the sandbox/development APNs environment. When distributing your app in the App Store, Ad Hoc or through App Store Connect, it will always use the production environment. If distributing through
Development
, the sandbox / development APNs environment is used. Keep in mind your distribution method when checking theUse APNS sandbox environment?
checkbox in an Ably application on the Ably dashboard (notifications tab). Ensure your application connects to an Ably application configured to use production APNs if the application is being distributed publicly.
- When running your application via Xcode or your machine (Android Studio, command line), your application runs in either debug, profile or release mode. In all cases, your application will use the sandbox/development APNs environment. When distributing your app in the App Store, Ad Hoc or through App Store Connect, it will always use the production environment. If distributing through
- Add
Push Notifications
capability: Click Runner in project navigator, clickRunner
target, under the Signing & Capabilities tab, click+ Capability
, and selectPush Notifications
. - Add
remote notification
Background mode:- Under the Signing & Capabilities tab, click
+ Capability
and selectBackground Modes
. - Check
remote notifications
.
- Under the Signing & Capabilities tab, click
- Ensure your application bundle ID is registered on App Store connect. This is done automatically by Xcode when you select your team under
- In your
AppDelegate.swift
orAppDelegate.m
, implementapplication:didFailToRegisterForRemoteNotificationsWithError:
. An example is shown in the example app,AppDelegate.m
. - During development, place a breakpoint in this method to diagnose why your device cannot register with APNs. This method will be called when there is an error, for example, if entitlements are not configured or when registering for APNs on the iOS simulator. You can check the
error
argument. If an error occurs in this method, Ably will not get the APNs device token. From theapplication(_:didFailToRegisterForRemoteNotificationsWithError:)
documentation:
UIKit calls this method if it was unable to register your app with APNs or if your app is not properly configured for remote notifications. During development, make sure your app has the proper entitlements and that its App ID is configured to support push notifications. You might use your implementation of this method to make a note of the failed registration so that you can try again later.
- Like the example app, you do not need to implement
UIApplication:registerForRemoteNotifications
, as the Ably plugin does this for you at app launch. - Handle messages received on your device by implementing the methods in your
AppDelegate
, such asdidReceiveRemoteNotification
,didReceiveNotificationResponse
andwillPresentNotification
. This is shown in the example app,AppDelegate.m
. For more information, have a look at the receiving messages section.
Devices need to be activated with Ably once. Once activated, you can use their device ID, client ID or push token (APNs device token/ FCM registration token) to push messages to them using the Ably dashboard or a Push Admin (SDKs which provide push admin functionality, such as Ably-java, Ably-js, etc.). However, to send push notifications through Ably channels, devices need to subscribe to a channel for push notifications. Once subscribed, messages on that channel with a push payload will be sent to devices which are subscribed to that channel.
The example app contains an example of how to use the Push Notification functionality in push_notification_service.dart
.
If you invoke any methods from the ably_flutter
package before calling runApp()
, you must call WidgetsFlutterBinding.ensureInitialized();
. This is done to ensure all platform methods will be successfully received by the native Ably plugin on the host platform.
- Create a rest or realtime client: e.g.
final realtime = ably.Realtime(options: clientOptions);
- Activate the device for push notifications with Ably:
ablyClient.push.activate();
. This only needs to be done once, and will be used across all future app launches, as long as the app is not deactivated. This method will throw an AblyException if it fails. - The
Future
returned byactivate
is not guaranteed to complete quickly. For example, if there is no internet connection,activate
will wait until it is available. Therefore, there is no guarantee any code awaiting the completion of theFuture
will run. - When moving between different environments (e.g. development vs. production), you should clear the push device information stored by Ably Flutter to avoid using the wrong push device information on a different environment, by either:
- Reinstalling your app, or
- Deactivating the device in the same environment the device was activated on.
try {
await push.activate();
} on ably.AblyException catch (error) {
// Handle/ log the error.
}
- Listen to push events: You should listen to the
Push.pushEvents.onUpdateFailed
stream to be informed when a new token update (FCM registration token / APNs device token) fails to be updated with Ably. If this update process fails, Ably servers will attempt to use the old tokens to send messages to devices and potentially fail.- Optional: listen to
Push.pushEvents.onActivate
andPush.pushEvents.onDeactivate
. This is optional becausePush.activate
andPush.deactivate
will return when it succeeds, and throw when it fails.
- Optional: listen to
void main() {
setUpPushEventHandlers();
runApp(MyApp());
}
void setUpPushEventHandlers() {
ably.Push.pushEvents.onUpdateFailed.listen((error) async {
print(error);
});
ably.Push.pushEvents.onActivate.listen((error) async {
print(error);
});
ably.Push.pushEvents.onDeactivate.listen((error) async {
print(error);
});
}
On iOS, conflicts with other dependencies or your native code might cause the Ably plugin to never receive the APNs device token.
This will cause calls to activate
to never complete (The future never completes with a success or error).
In this case, you should provide us with the device token manually in your didRegisterForRemoteNotificationsWithDeviceToken
and didFailToRegisterForRemoteNotificationsWithError
in your application delegate.
If you're working in Objective-C then this will look something like this in AppDelegate.m
:
#import "AblyFlutter.h"
@implementation AppDelegate
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[AblyInstanceStore.sharedInstance didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
AblyInstanceStore.sharedInstance.didFailToRegisterForRemoteNotificationsWithError_error = error;
}
@end
If you're working in Swift then this will look something like this in AppDelegate.swift
:
import ably_flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
AblyInstanceStore.sharedInstance().didRegisterForRemoteNotifications(withDeviceToken: deviceToken)
}
override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
AblyInstanceStore.sharedInstance().didFailToRegisterForRemoteNotificationsWithError_error = error;
}
}
- Get the Realtime/ Rest channel:
final channel = realtime!.channels.get(Constants.channelNameForPushNotifications)
- Subscribe the device to the push channel, by either using the device ID or client ID:
channel.push.subscribeClient()
orchannel.push.subscribeDevice()
- This is different to subscribing to a channel for messages.
- Your device is now ready to receive and display user notifications (called alert notifications on iOS and notifications on Android) to the user, when the application is in the background.
- For debugging: You could use the Ably dashboard (notification tab) or the Push Admin API using another SDK to ensure the device has been subscribed by listing the subscriptions on a specific channel. Alternatively, you can list the push channels the device or client is subscribed to:
final subscriptions = channel.push.listSubscriptions()
. This API requires Push Admin capability, and should be used for debugging. This means you must use an Ably API key or token with thepush-admin
capability.
Requesting permissions:
- Understand the iOS platform behavior:
- The first time your app makes this authorization request, the system prompts the user to grant or deny the request and records the user’s response. Subsequent authorization requests don’t prompt the user. - Asking Permission to Use Notifications
- This means it is important to choose the moment you request permission from the user. Once a user denies permission, you would need to ask the user to go into the Settings app and give your app permission manually.
- Request permissions using
Push#requestPermission
, passing in options such as badge, sound, alert and provisional. See API documentation for more options. - To avoid showing the user a permission alert dialog, you can request provisional permissions with
Push#requestPermission(provisional: true)
. The notifications will be delivered silently to the notification center, where the user will be able opt-in to deliver messages from your app prominently in the future.
// Create an Ably client to access the push field
final realtime = ably.Realtime(options: clientOptions);
final push = realtime.push;
// Request permission from user on iOS
bool permissionGranted = await push.requestPermission();
// Get more information about the notification settings
if (Platform.isIOS) {
final notificationSettings = await realtime.push.getNotificationSettings();
}
Shows a notification to the user immediately when it is received by their device.
Android: This is known as a notification message. A notification message cannot be customised or handled (e.g. run logic when a user taps the notification) - therefore, if you need to handle user taps or customize a notification, send a data message and create a local notification.
iOS: This is known as an alert push notification. An alert notification can be customised.
To send a user notification, publish the following message to the channel:
final message = ably.Message(
data: 'This is an Ably message published on channels that is also sent '
'as a notification message to registered push devices.', // Optional data field sent not sent in push messages.
extras: const ably.MessageExtras({
'push': {
'notification': {
'title': 'Hello from Ably!',
'body': 'Example push notification from Ably.',
'sound': 'default',
},
},
}));
Allows you to run logic in your application, such as download the latest content, perform local processing and creating a local notification.
Android: This is known as a data message.
iOS: This is known as a background notification. These messages must have a priority of 5
, a push-type of background
, and the content-available
set to 1
, as shown in the code snippet below. To learn more about the message structure required by APNs, read
Pushing Background Updates to Your App. You may see this documented as "silent notification" in Firebase documentation.
On iOS, a background notification may not be throttled to 2 or 3 messages per hour, or limited for other reasons (for example, your app was just installed recently). To ensure your messages arrive promptly, you may send a message with both notification and data, which will show a notification to the user.
final _pushDataMessage = ably.Message(
data: 'This is a Ably message published on channels that is also '
'sent as a data message to registered push devices.',
extras: const ably.MessageExtras({
'push': {
'data': {'foo': 'bar', 'baz': 'quz'},
'apns': {
'apns-headers': {
'apns-push-type': 'background',
'apns-priority': '5',
},
'aps': {
'content-available': 1
}
}
},
}));
Only use high priority when it requires immediate user attention or interaction. Use the normal priority (5) otherwise. Messages with a high priority wake a device from a battery saving state, which drains the battery even more.
- High priority:
'priority': 'high'
insidepush.fcm.android
for Android.apns-priority: '10'
insidepush.apns.apns-headers
for iOS. - Normal priority:
'priority': 'normal'
insidepush.fcm.android
for Android.apns-priority: '5'
insidepush.apns.apns-headers
for iOS.
Push notifications containing both the notification and data objects will be treated as both alert notifications and data messages.
final message = ably.Message(
data: 'This is an Ably message published on channels that is also sent '
'as a notification message to registered push devices.',
extras: const ably.MessageExtras({
'push': {
'notification': {
'title': 'Hello from Ably!',
'body': 'Example push notification from Ably.',
'sound': 'default',
},
'data': {'foo': 'bar', 'baz': 'quz'},
'apns': {
'aps': {'content-available': 1}
},
}));
For examples of handling incoming messages and dealing with notifications, see push_notification_handlers in the example app. There are 2 types of push notifications:
- Notifications to your user (alert push notification on iOS, notification message on Android), and
- Notifications to your app (background notification on iOS, data message on Android)
Android: If the app is in the background / terminated, you cannot configure / disable notification messages as they are automatically shown to the user by Firebase Messaging Android SDK. To create notifications which launch the application to a certain page (notifications which contain deep links or app links), or notifications which contain buttons / actions, images, and inline replies, you should send a data message and create a notification when the message is received.
iOS: If the app is in the background / terminated, ably-flutter doesn't provide the functionality to configure / extend alert notifications on iOS, and these will automatically be shown to the user.
To create local notifications with more content or user interaction options once a data message is received on both Android and iOS, take a look at awesome_notifications.
- Advanced usage:
- To do this natively on Android instead, you could use
notificationManager.notify
. - To do this natively on iOS, you can send a background message and follow Scheduling a Notification Locally from Your App. However, on iOS, you could also customize the appearance of an alert notification, by registering and implementing a
UNNotificationContentExtension
.
- To do this natively on Android instead, you could use
The SDK does this by implementing userNotificationCenter(_:willPresent:withCompletionHandler:)
. This feature is not available on Android.
- On iOS, if a notification is also present, it will be shown
- before
onMessage
is called, and - before the callback you set using
ably.Push.notificationEvents.setOnBackgroundMessage
is called.
- before
- On Android, if a notification is also present, it will be shown
- after
onMessage
is called, and - after the callback you set using
ably.Push.notificationEvents.setOnBackgroundMessage
is called.
- after
- When the app is in the foreground, you can listen to messages using:
ably.Push.notificationEvents.onMessage.listen((remoteMessage) {
print('Message was delivered to app while the app was in the foreground: '
'$remoteMessage');
});
-
This method can be synchronous or
async
. -
When the app is terminated or in the background, you can listen to messages using:
Future<void> _backgroundMessageHandler(ably.RemoteMessage message) async {
print('Just received a background message, with:');
print('RemoteMessage.Notification: ${message.notification}');
print('RemoteMessage.Data: ${message.data}');
}
ably.Push.notificationEvents.setOnBackgroundMessage(_backgroundMessageHandler);
- This method can be synchronous or
async
. - On Android, when a message is received whilst the app is terminated, Ably Flutter will launch your application to process the message. Ably Flutter listens to the message by registering a broadcast receiver. This means your
MainActivity
will not be launched, and so your native setup code (e.g. creating method channels) you may have written in yourMainActivity
'sconfigureFlutterEngine
method will not be run. If you want yourconfigureFlutterEngine
to run even when Ably launches your application, refactor this code into a Flutter package plugin, implementingFlutterPlugin
and move your logic insideonAttachedToEngine
.
- To be informed when a notification tap launches the app:
ably.Push.notificationEvents.notificationTapLaunchedAppFromTerminated
.then((remoteMessage) {
if (remoteMessage != null) {
print('The app was launched by the user by tapping the notification');
print(remoteMessage.data);
}
});
- To be informed when a notification is tapped by the user whilst the app is in the foreground:
ably.Push.notificationEvents.onNotificationTap.listen((remoteMessage) {
print('Notification was tapped: $remoteMessage');
});
On iOS, ably-flutter allows you to configure notification sent directly from Ably to be shown when the app is in foreground. To configure if a notification is shown in the foreground:
ably.Push.notificationEvents.setOnShowNotificationInForeground((message) async {
// TODO add logic to show notification based on message contents.
print('Opting to show the notification when the app is in the foreground.');
return true;
});
Users can listen to messages in each platform using the native message listeners instead of Dart listeners. This is not recommended unless you want to avoid using other plugins, such as awesome_notifications and flutter_local_notifications. Another reason to use your own native message handling is to avoid conflicts with other dependencies.
Android: This requires you to implement FirebaseMessageService
and override the onMessageReceived
method. You must also declare this service in your AndroidManifest.xml
. Once you receive your message, you could create a notification. As declaring this service would override the service used internally by Ably Flutter, be sure to provide Ably Flutter with your registration token. Implement the following onNewToken
method in FirebaseMessagingService
:
@Override
public void onNewToken(@NonNull String registrationToken) {
ActivationContext.getActivationContext(this).onNewRegistrationToken(RegistrationToken.Type.FCM, registrationToken);
super.onNewToken(registrationToken);
}
Then, in your Android Manifest, disable Ably Flutter's broadcast receiver by removing it from your Manifest using tools:node="remove"
. This will prevent Ably from listening for new push notifications and launching your Flutter application. You can do this by adding the following between <application>
and </application>
:
<receiver android:name="io.ably.flutter.plugin.push.FirebaseMessagingReceiver"
tools:node="remove">
</receiver>
iOS: Implementing the didReceiveRemoteNotification
delegate method declared in UIApplicationDelegate
.
Take a look at the example app platform specific code to handle messages. For iOS, this is AppDelegate.m
, and in Android, it is PushMessagingService.java
. For further help on implementing the Platform specific message handlers, see "On Android" and "On iOS" sections on Push Notifications - Device activation and subscription.
- For tips on how best to use push messaging on Android, read Notifying your users with FCM. For example:
- Show a notification to the user as soon as possible without any additional data usage or processing. Perform additional synchronization work asynchronously after that, using workmanager. You can also replace the notification with a new one with more content and user interaction options.
- Avoid background services: As recommended by FCM, Ably Flutter does not instantiate any background services or schedule any jobs on your behalf. Libraries and applications which do this, for example Firebase Messaging may face
IllegalStateException
exceptions and reduced execution time.
- For more Android tips, read About FCM messages
Android: If you use other push notification packages alongside Ably or declare a push notification service in your AndroidManifest.xml
, you may face compatibility issues. If a service is declared listening for the intent action string "com.google.firebase.MESSAGING_EVENT"
, either from a different library or your own implementation, you will override the one implemented by Ably. Only one service in an application can receive an intent. On Android, merely installing certain Flutter packages may result in services being registered and overriding each other (e.g. firebase_messaging). Be careful to look at the merged manifest in Android Studio to evaluate if the Ably service is still declared in the merged manifest. Ably Flutter declares a service in it's own AndroidManifest.xml
which gets merged into your application manifest. If your merged manifest does not show Ably Flutter's service, Ably Flutter will not receive FCM registration tokens. Therefore, you need to provide Ably with the fcm registration token by using ActivationContext.getActivationContext(this).onNewRegistrationToken(RegistrationToken.Type.FCM, registrationToken);
.
iOS: Some iOS dependencies perform techniques such as method swizzling which break other iOS dependencies and Flutter Plugin packages.
Do this only if you do not want the device to receive push notifications at all. You usually do not need to run this at all.
try {
await push.deactivate();
} on ably.AblyException catch (error) {
// Handle/ log the error.
}
During development, you may want to validate or debug situations where your application is either in the terminated state, in the background or in the foreground.
Android: Go into developer options and use the "Wait for debugger" option. Launch the app from the Flutter project (not android
directory) in Android Studio. This ensures any --dart-define
variables are used by the application. Then in the Android project in Android Studio, attach the debugger. This allows you to debug Java code, such as the message handlers. Then "Attach Flutter" in Android Studio or CLI if you want to, as well.
iOS: In Xcode, configure your Xcode app scheme's launch option to "Wait for the executable to be launched" instead of "Automatically". Then "Attach Flutter" in Android Studio or CLI if you want to, as well.
The platform SDKs (ably-android and ably-cocoa) enable users to check if device activation, deactivation, or registration update fails. On Android, these errors are sent in Intents which users should register for at runtime. In Cocoa, errors are returned through ARTPushRegistererDelegate
methods. However, in both SDKs, this error does not always return quickly. For example, if there was no internet connection, then Push.activate()
will not throw an error, it will just block forever, because errors are not provided by the SDKs. Once an internet connection is made, the Intent will be sent and delegate methods will be called.
Ably Flutter does this by passing a reference to the FlutterResult
used to pass back the result to the Dart side, when the activation or deactivation completes. This makes it convenient for users: they can await push.activate()
. However, users should not rely on this Future completing, in the case of network issues.
Android's Firebase Messaging library enables users to select the Intent action used when the automatically-generated notification is tapped by the user. Users can do this by setting fcm.notification.click_action
in Ably's push payload. However, for this to work, users would need to declare the intent action within their AndroidManifest.xml
. Therefore, we don't really tell users they can modify click_action
and configure it. However, they can do so if they wish.
iOS enables users to show the notification received remotely even if the app is in the foreground, by calling a delegate method (userNotificationCenter(_:willPresent:withCompletionHandler:)
) which the user can choose to show the message to the user, based on the notification content. FCM does not provide this functionality. Users can only configure this behaviour for iOS, by using PushNotificationEvents#setOnShowNotificationInForeground
.
Background processing in Flutter is a complicated subject that has not been explored publicly in detail. It involves creating Dart isolates manually (for Android), passing messages back and forth, etc.
Differences between Ably Flutter and Firebase Messaging implementation (Android only):
- Isolate code: Firebase Messaging explicitly defines a callback which is launched in a custom isolate. Ably Flutter does not launch a custom dart entrypoint, but instead re-uses the user's default entrypoint (their Flutter application), by using
DartExecutor.DartEntrypoint.createDefault()
. Therefore, Ably Flutter provides the same environment for message handling on both Android and iOS: users application is running when we handle the message. - Resource consumption tradeoffs: Firebase launches an isolate capable of only handling messages at app launch, even if users' application isn't handling remote messages. Firebase Messaging keeps this isolate running throughout the app, and have a queue process to queue messages by maintaining an Android Service. This allows 10 minutes of execution time, where as on iOS, Firebase Messaging only has 30 seconds of execution time. Instead, Ably Flutter launches a new isolate on every message if the application is not yet running and avoids creating a service and queueing work. A new message will spawn a new engine.
- Execution time: Ably Flutter provides users with an execution time of 30 seconds on both Android and iOS to handle each message. On Android, Firebase messaging launches a Service which has approximately 10 minutes of execution time from it's launch to handle all messages received before Android stops the service. It's unclear if the Service will be automatically launched by iOS immediately, or if it will only be launched in the future. On iOS, each message has 30 seconds of execution time. This seems to be a bug in the design of firebase_messaging. Users can extend their execution time by using package:workmanager.
Because of this architectural simplicity, Ably Flutter does not need to use PluginUtilities
, pass references of two methods between Dart and host platform, or save and load these methods in SharedPreferences
. Ably Flutter avoids conflicts between the default FlutterEngine
launched with a FlutterActivity
and the one manually launched in the BroadcastReceiver
, by using method channels with unique channel names.
If sending a push message from a channel, ensure your device ID or client ID is subscribed to that channel. After sending a message with a push payload on an ably channel, check the push state of the device either on the Ably Dashboard or using a Push Admin.
On iOS, to show alert notifications, you need to request provisional permission or explicit permission from the user. When debugging further, we recommend using the Console.app (this is different to Terminal.app or iTerm2.app) installed on your mac.
- To confirm your application received the push message/ check for errors related to push notifications, find relevant logs by:
- search for the following log messages:
- Both failures and success:
com.apple.pushLaunch
- Failures only:
CANCELED: com.apple.pushLaunch
. For example, this may show the log line: CANCELED: com.apple.pushLaunch.com.example.app:DBA43D at priority 10 ! - Success only:
COMPLETED com.apple.pushLaunch.package_name:XXXXXX at priority 5 <private>!
- Both failures and success:
- filter for
dasd
process either by right clicking a log line withdasd
and clickShow Process 'dasd'
. - If you are sending a background notification, it may be throttled by iOS. If the message is being throttled, it will eventually arrive to your application, and your
didReceiveRemoteNotification
message will be called, often within a few minutes. If you look in the Console.app logs, you may find sending the exact same message gives different outcomes:ThunderingHerdPolicy
error:
{name: ThunderingHerdPolicy, policyWeight: 1.000, response: {Decision: Must Not Proceed, Score: 0.00, Rationale: [{deviceInUse == 1 AND timeSinceThunderingHerdTriggerEvent < 900}]}} ], FinalDecision: Must Not Proceed}
cameraIsActive
error:
com.apple.pushLaunch.io.ably.flutter.plugin-example:4935F4:[ {name: MemoryPressurePolicy, policyWeight: 5.000, response: {Decision: Must Not Proceed, Score: 0.00, Rationale: [{cameraIsActive == 1}]}} ], FinalDecision: Must Not Proceed}
- A successful delivery:
{name: ApplicationPolicy, policyWeight: 50.000, response: {Decision: Absolutely Must Proceed, Score: 1.00, Rationale: [{[appIsForeground]: Required:1.00, Observed:1.00},]}} ], FinalDecision: Absolutely Must Proceed}
- Another successful delivery:
com.apple.pushLaunch.io.ably.flutter.plugin-example:5E1C66:[ {name: DeviceActivityPolicy, policyWeight: 5.000, response: {Decision: Can Proceed, Score: 0.25}} ] sumScores:93.270000, denominator:97.020000, FinalDecision: Can Proceed FinalScore: 0.961348}
- search for the following log messages:
On Android, you can use logcat built into Android Studio or pidcat to view the logs.
I have confirmed messages are not being received by the device (no logs of being cancelled), but no errors are thrown when I send the message
Sending an Ably message with a push payload will succeed if the message is successfully delivered to Ably, including delivery guarantees for channel subscriptions. However, push notification messages may not be delivered to specific devices if there is an error returned by APNs / FCM. You should retrieve the device registration using a Push Admin or the Ably Dashboard. For example, if your application has a bundle ID which does not match APNs certificate generated from developer.apple.com, you would get DeviceTokenNotForTopic
, on the Ably dashboard, this would look like:
For iOS device registrations, the device push state error are errors passed directly from APNs. For a full list of errors and what they mean, look at Values for the APNs JSON reason
key.
For Android device registrations, the device push state error are errors passed directly from FCM. For a full list of errors and what they mean, look at Downstream message error response codes.
When the app is in the foreground (open by the user), Firebase messaging ignores the message. You would need to send a data message and build a local notification instead. On iOS, you can specify this in your UNUserNotificationCenterDelegate
's userNotificationCenter:_willPresentNotification:withCompletionHandler
method. In the example app, this is implemented in AppDelegate.m
, where the notification is always shown. You can perform logic to decide if it should be shown or not based on the notification.
Messaging generated from the "compose notification" in Firebase cloud messaging console are not received.
Ensure your Android app contains the Firebase configuration android/app/google-services.json
file. You can download this from your Firebase project settings.
"FCM Reporting dashboard" in Firebase cloud messaging console does not show any messages being received.
You need to add the firebase-analytics dependency to your app/build.gradle
file. This was optional when following the Firebase Android client setup guide, for example: implementation 'com.google.firebase:firebase-analytics:version_number'
. Find the latest version number from MVNRepository.
When retrieving a device registration using a Push Admin or using the Ably dashboard, the device push state is is e.g. BadDeviceToken
?
In this case, the device token is invalid. Make sure the environment for push notifications on the app (Runner.entitlements
) matches the environment set in Ably dashboard (push notification tab).
The specified device token was bad. Verify that the request contains a valid token and that the token matches the environment.
When running a debug application, the sandbox / development APNs server is used. Make sure to use an application with Use APNS sandbox environment?
enabled in the Ably dashboard (push notification tab). Changing the aps-environment
value in the .entitlements
file to production
does not make the debug application use the production APNs server.
Development / Sandbox APNs: Local builds through Xcode / Android Studio / command line will always get sandbox APNs tokens. Apps distributed through
Development
methods will also get sandbox APNs tokens.Production APNs: Apps distributed through App Store Connect (TestFlight and App Store), Ad Hoc and Enterprise distribution methods will always get production APNs tokens.
For more information, take a look at What are the possible reasons to get APNs responses BadDeviceToken or Unregistered?.
Android: When retrieving a device registration using a Push Admin or using the Ably dashboard, the device push state is is InvalidRegistration
This means your registration token is invalid. Ably is may not have your device's FCM registration token. FirebaseMessagingService.onNewToken
is only called when a new token is available, so if Ably was installed in a new app update and the token has not been changed, Ably won't know it. If you have previously registered with FCM without Ably, you should make sure to give ably the latest token, by getting it and calling:
ActivationContext.getActivationContext(this).onNewRegistrationToken(RegistrationToken.Type.FCM, registrationToken);
Android: Ensure you have set up a NotificationChannel
(which displays as notification "categories" to Android users within your application settings). The example app shows how to do this by using flutter_local_notifications to configure AndroidNotificationChannel
.
iOS: Ensure you set the sound
key in the push.notification
object when sending the Ably message.
When building my application, I get the following error: Manifest merger failed : uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [:ably_flutter]
A fresh Flutter project generates an Android project with a minSdkVersion
of 16, but Ably-flutter only supports API level 19 (Android Kitkat 4.4, which was released in 2013) and above. In android/app/build.gradle
, change minSdkVersion 16
to minSdkVersion 19
.