Closed takatomatsumura closed 3 weeks ago
Duplicate of: https://github.com/firebase/flutterfire/issues/13442
@takatomatsumura I have the same issue, onBackgroundMessage only works in debug mode on some devices. Did you find a solution?
I've tested a bunch of devices:
@pvc97 In my case, the cause of the issue has been identified, but the solution is unclear.
I reviewed the source code of firebase_admin to understand it and investigate the cause.
FirebaseMessaging.onBackgroundMessage
is worked correctly, but it is likely that throttling in release mode is the reason that FirebaseMessaging.onBackgroundMessage
is not invoked.
You can find more details about Apple notifications Apple Developer "The Push Notifications primer".
To summarize about background notifications, background notifications are largely similar to alert notifications, but there are some crucial differences. Specifically, background notifications are limited by daily send limits, and they may not function under certain conditions, such as when the device has low battery.
This is also mentioned in this documentation about Pushing background updates to your App, that documentation states that the limit is 2 or 3 background notifications per day.
I couldn't understand the reason why FirebaseMessaging.onBackgroundMessage
sometimes was being called and sometimes not, so I decided to investigate further.
In iOS, we are able to listen to receive notification by didReceiveRemoteNotification.
Below, this is iOS native source code of firebase_messaging for invoking flutter FirebaseMessaging.onBackgroundMessage
.
#if !TARGET_OS_OSX
// Called for silent messages (i.e. data only) in the foreground & background
- (BOOL)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
#if __has_include(<FirebaseAuth/FirebaseAuth.h>)
if ([FIRApp defaultApp] != nil && [[FIRAuth auth] canHandleNotification:userInfo]) {
completionHandler(UIBackgroundFetchResultNoData);
return YES;
}
#endif
NSDictionary *notificationDict =
[FLTFirebaseMessagingPlugin remoteMessageUserInfoToDict:userInfo];
// Only handle notifications from FCM.
if (userInfo[@"gcm.message_id"]) {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
__block BOOL completed = NO;
// If app is in background state, register background task to guarantee async queues aren't
// frozen.
UIBackgroundTaskIdentifier __block backgroundTaskId =
[application beginBackgroundTaskWithExpirationHandler:^{
@synchronized(self) {
if (completed == NO) {
completed = YES;
completionHandler(UIBackgroundFetchResultNewData);
if (backgroundTaskId != UIBackgroundTaskInvalid) {
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
}
}
}
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(25 * NSEC_PER_SEC)),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@synchronized(self) {
if (completed == NO) {
completed = YES;
completionHandler(UIBackgroundFetchResultNewData);
if (backgroundTaskId != UIBackgroundTaskInvalid) {
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
}
}
}
});
// This is my comment
// invoke flutter backgroundHandler here
[_channel invokeMethod:@"Messaging#onBackgroundMessage"
arguments:notificationDict
result:^(id _Nullable result) {
@synchronized(self) {
if (completed == NO) {
completed = YES;
completionHandler(UIBackgroundFetchResultNewData);
if (backgroundTaskId != UIBackgroundTaskInvalid) {
[application endBackgroundTask:backgroundTaskId];
backgroundTaskId = UIBackgroundTaskInvalid;
}
}
}
}];
} else {
// If "alert" (i.e. notification) is present in userInfo, this will be called by the other
// "Messaging#onMessage" channel handler
if (userInfo[@"aps"] != nil && userInfo[@"aps"][@"alert"] == nil) {
// This is my comment
// invoke flutter onMessageHandler here
[_channel invokeMethod:@"Messaging#onMessage" arguments:notificationDict];
}
completionHandler(UIBackgroundFetchResultNoData);
}
return YES;
} // if (userInfo[@"gcm.message_id"])
return NO;
} // didReceiveRemoteNotification
#endif
Then, I debugged to figure out whether the cause is in the iOS native code or the Flutter code by XCode and console-app of mac.
The firebase_messaging 15.1.4 was released, so I used firebase_messaging 15.1.4 of latest version.
I used firebase-admin for sending fcm message.
The cause of not invoking FirebaseMessaging.onBackgroundMessage
is that didReceiveRemoteNotification
handler is not invoked. So, FirebaseMessaging.onBackgroundMessage
is not called.
Debug result is also same as my report in the above, it seems that there is no throttling in debug-mode. I send message more than 20 message in a hour, all message was received.
However, as mentioned in the documentation, it seems that there is throttling in release-mode. I was throttled after sending it twice.
@takatomatsumura
This is also mentioned in this documentation about Pushing background updates to your App
This doc is about "Background notification":
A background notification is a remote notification that doesnโt display an alert, play a sound, or badge your appโs icon. It wakes your app in the background and gives it time to initiate downloads from your server and update its content.
However, you are sending messages that include both a notification title and content, and iOS can display them. I believe that a background notification refers to a message without notification content (only data). Therefore, throttling should not occur in this case.
@pvc97 As you mentioned, I sent the push notification as an alert notification.
According to the FCM documentation, there are the following types of notifications:
Also in the Apple documentation for Sending notification requests to APNs, the following types are mentioned:
I think, one notification could have the nature of multiple types such as alert notification & background notification, like Notification & Data.
In the Apple documentation for Generating a remote notification, alert and content-available are defined as follows:
I believe that if both alert
and content-available
are specified, it is likely that the alert notification will be shown to the user, and the background processing will run as a background notification.
In my code, as defined above, since the content to be displayed to the user is specified in alert
, it is an alert notification. And since content-type
is also specified, it is probably considered a background notification as well.
message = messaging.Message(
notification=messaging.Notification(
title="TEST TITLE",
body="This is test notification for iOS background throttling",
),
data={"hello": "This is a Firebase Cloud Messaging device group message!"},
...
apns=messaging.APNSConfig(
payload=messaging.APNSPayload(
aps=messaging.Aps(
content_available=True,
sound="default",
),
),
headers={"apns-priority": "5"},
),
...
)
If it is processed as a background notification, throttling may occur on the device.
Is there an existing issue for this?
Are you aware of the differences between iOS and Android background message handling?
Do you have an active Apple Developer account?
Are you using a physical iOS device to test background messages?
Have you enabled "Remote Notifications" & "Background Mode" (Checking options for "Background Processing" & "Remote Notifications") in your app's Xcode project?
Have you created an APNs key in your Apple Developer account & uploaded this APNs key to your Firebase console?
Have you disabled method swizzling for Firebase in your app?
Are you sending messages to your app from the Firebase Admin SDK?
I use firebase example for sending message. My snippet is below.
I add required headers, such as "priority" on Android and "content-available" on iOS.
Have you requested permission from the user to receive notifications?
Have you used the 'Console' application on your macOS device to check if the iOS device's system is throttling your background messages?
ใใใฉใซใ 04:32:50.662028+0900 SpringBoard Received incoming message on topic com.example at priority 10 ใใใฉใซใ 04:32:50.673029+0900 SpringBoard [com.example] Received remote notification request 7E65-E90C [ waking: 0, hasAlertContent: 1, hasSound: 0 hasBadge: 0 hasContentAvailable: 1 hasMutableContent: 0 pushType: Alert] ใใใฉใซใ 04:32:50.673408+0900 SpringBoard [com.example] Process delivery of push notification 7E65-E90C ใใใฉใซใ 04:32:50.673753+0900 SpringBoard [com.example] Request DUET delivers content-available push notification to application ใใใฉใซใ 04:32:50.674520+0900 SpringBoard SUBMITTING: com.apple.pushLaunch.com.example:39932D ใใใฉใซใ 04:32:50.675441+0900 SpringBoard [com.example] Badge can be set for notification 7E65-E90C: 0 [ canBadge: 1 badgeNumber: (null) ] ใใใฉใซใ 04:32:50.675678+0900 SpringBoard Getting effectiveSectionInfo for section identifier: com.example ใใใฉใซใ 04:32:50.675880+0900 SpringBoard [com.example] Getting effective section info ใใใฉใซใ 04:32:50.677206+0900 dasd Submitted Activity: com.apple.pushLaunch.com.example:39932D at priority 10 (Sun Nov 3 04:32:50 2024 - Mon Nov 4 04:32:50 2024) ใใใฉใซใ 04:32:50.677639+0900 SpringBoard [com.example] Got effective section info [ hasResult: 1 ] ใใใฉใซใ 04:32:50.677850+0900 SpringBoard Getting effectiveSectionInfo for section identifier: com.example ใใใฉใซใ 04:32:50.678046+0900 SpringBoard [com.example] Getting effective section info ใใใฉใซใ 04:32:50.679086+0900 SpringBoard [com.example] Got effective section info [ hasResult: 1 ] ใใใฉใซใ 04:32:50.679431+0900 SpringBoard [com.example] Saving notification 7E65-E90C: YES [ hasAlertContent: YES, shouldPresentAlert: YES settingsShouldSave: YES] ใใใฉใซใ 04:32:50.680068+0900 dasd Daemon Canceling Activities: {( com.apple.pushLaunch.com.example:731C6D )} ใใใฉใซใ 04:32:50.680233+0900 dasd CANCELED: com.apple.pushLaunch.com.example:731C6D at priority 10!
ใใใฉใซใ 04:32:50.718648+0900 SpringBoard [com.example] Delivered user visible push notification 7E65-E90C
ใใใฉใซใ 04:32:50.727308+0900 SpringBoard [com.example] Load 0 pending notification dictionaries
ใใใฉใซใ 04:32:50.727812+0900 SpringBoard [com.example] Adding notification 7E65-E90C [ hasAlertContent: 1, shouldPresentAlert: 1 hasSound: 0 shouldPlaySound: 1 ]; interruption-level: 1; destinations 398: (
NotificationCenter,
LockScreen,
Alert,
Spoken,
Forwarding
)
ใใใฉใซใ 04:32:50.728473+0900 SpringBoard BBDataProviderProxy com.example has enqueued a bulletin request
ใใใฉใซใ 04:32:50.728648+0900 SpringBoard BBDataProviderProxy com.example is now sending enqueued bulletin request to BBServer
ใใใฉใซใ 04:32:50.729482+0900 SpringBoard Publishing bulletin for section : subsectionIDs: (null), bulletinID = <64AD7851-AF33-4FE4-946C-B3E3ACACFAE3>, expiration date = <Sun Nov 10 04:32:50 2024>, expiration events <0>
ใใใฉใซใ 04:32:50.734751+0900 SpringBoard NCBulletinNotificationSource adding bulletin 7E65-E90C for feed 3115 in section com.example
ใใใฉใซใ 04:32:50.743469+0900 SpringBoard [com.apple.springboard.NCBulletinNotificationSource:0BB2CF28-AE1D-4DAE-8C8B-F6A5752A93DD] Resolving behavior for event, details=<DNDMutableClientEventDetails: 0x303cb9720; identifier: 'E0346579-3961-40F5-ADC6-B54BD8CA6576'; bundleIdentifier:: com.example; type: Default; urgency: Default; sender: (null); threadIdentifier: E3BBBB1022DC9959; filterCritera: (null); notifyAnyway: 0; behavior: Default>
ใใใฉใซใ 04:32:50.744395+0900 donotdisturbd Event was resolved: resolution=<DNDSEventBehaviorResolution: 0x66492ce40; UUID: D9574973-7427-4816-95FD-7D2EA22124DA; date: 2024-11-02 19:32:50 +0000; eventBehavior: <DNDClientEventBehavior: 0x6642d7e70; eventDetails: <DNDClientEventDetails: 0x6648f4dc0; identifier: 'E0346579-3961-40F5-ADC6-B54BD8CA6576'; bundleIdentifier:: com.example; type: Default; urgency: Default; sender: (null); threadIdentifier: E3BBBB1022DC9959; filterCritera: (null); notifyAnyway: 0; behavior: Default>; interruptionSuppression: none; resolutionReason: disabled; activeModeUUID: (null)>; clientIdentifier: 'com.apple.springboard.NCBulletinNotificationSource'; outcome: allowed; reason: disabled>
ใใใฉใซใ 04:32:50.745058+0900 SpringBoard [com.apple.springboard.NCBulletinNotificationSource:0BB2CF28-AE1D-4DAE-8C8B-F6A5752A93DD] Resolved event, details=<DNDMutableClientEventDetails: 0x303cb9720; identifier: 'E0346579-3961-40F5-ADC6-B54BD8CA6576'; bundleIdentifier:: com.example; type: Default; urgency: Default; sender: (null); threadIdentifier: E3BBBB1022DC9959; filterCritera: (null); notifyAnyway: 0; behavior: Default> behavior=<DNDClientEventBehavior: 0x30119b6c0; eventDetails: <DNDClientEventDetails: 0x303fffb60; identifier: 'E0346579-3961-40F5-ADC6-B54BD8CA6576'; bundleIdentifier:: com.example; type: Default; urgency: Default; sender: (null); threadIdentifier: E3BBBB1022DC9959; filterCritera: (null); notifyAnyway: 0; behavior: Default>; interruptionSuppression: none; resolutionReason: disabled; activeModeUUID: (null)>
ใใใฉใซใ 04:32:50.745439+0900 SpringBoard Posting notification id: 7E65-E90C; section: com.example; thread: E2B6-531F; category: ; timestamp: 2024-11-02 19:32:50 +0000; interruption-level: active; relevance-score: 0.00; filter-criteria: (null); actions: [ minimal: 0 (0 text), default: 0 (0 text) ]; destinations: [ {(
BulletinDestinationCoverSheet,
BulletinDestinationBanner,
BulletinDestinationNotificationCenter,
BulletinDestinationLockScreen
)} ]
ใใใฉใซใ 04:32:50.747673+0900 SpringBoard Incoming Section [NCNotificationStructuredSectionList] inserting notification request 7E65-E90C in existing group Group List [com.example:E2B6-531F] at index 0
ใใใฉใซใ 04:32:50.747879+0900 SpringBoard Group List [com.example:E2B6-531F] inserting notification request 7E65-E90C at index 0
Additional context and comments
I referenced the Issue 13525, and I specified
pubspec.yaml
below. I used the version in which the issue is reported to be resolved. The notifications themselves are being received in any state, so the settings for FCM should be correct.I checked some pattern, combination build-mode(debug or profile or release) and app-state(foreground, background with app-alive, background with app-killed). I found success message in 'Console' application on macOS 'COMPLETED com.example:5EBC1B at priority 5!'.
In my case, under background with app-alive state and debug-mode,!';
It occasionally works, but the conditions are unclear.
FirebaseMessaging.onBackgroundMessage
was invoked correctly. However, under background with app-alive and release-mode or background with app-alive and release-mode was not invoked. As mentioned above, I found cancelled background task message 'CANCELED: com.apple.pushLaunch.com.example:731C6D at priority 5In background-mode and profile-build,
FirebaseMessaging.onBackgroundMessage
was invoked and worked correctly.I would like to add that, all build-mode and foreground-state is work correctly.
I also tested the latest version 15.1.3 as of November 2024, as well as the currently used version 15.0.4, but they did not work properly. I tried on iOS 18 and 17, but did not observe any significant differences between the OS versions.