Skip to content

Commit

Permalink
[iOS] Handle notification messages that contain "content-available":1…
Browse files Browse the repository at this point in the history
… which wakes up the app while in the background to deliver the message payload immediately when the message arrives (without requiring user interaction by tapping the system notification).

Fixes #158.
  • Loading branch information
dpa99c committed Oct 22, 2019
1 parent 0ddf201 commit cb3dda2
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 10 deletions.
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ To help ensure this plugin is kept updated, new features are added and bugfixes
- [Android Notification Color](#android-notification-color)
- [Android Notification Sound](#android-notification-sound)
- [iOS notifications](#ios-notifications)
- [iOS background notifications](#ios-background-notifications)
- [iOS notification sound](#ios-notification-sound)
- [iOS badge number](#ios-badge-number)
- [Data messages](#data-messages)
Expand Down Expand Up @@ -386,8 +387,11 @@ Note: only notification messages can be sent via the Firebase Console - data mes

### Background notifications
If the notification message arrives while the app is in the background/not running, it will be displayed as a system notification.
No callback can be made by the plugin to the app when the message arrives since the display of the notification is entirely handled by the operating system.
However if the user taps the system notification, this launches/resumes the app and the notification title, body and optional data payload is passed to the [onMessageReceived](#onMessageReceived) callback.

By default, no callback is made to the plugin when the message arrives while the app is not in the foreground, since the display of the notification is entirely handled by the operating system.
However, there are platform-specific circumstances where a callback can be made when the message arrives and the app is in the background that don't require user interaction to receive the message payload - see [Android background notifications](#android-background-notifications) and [iOS background notifications](#ios-background-notifications) for details.

If the user taps the system notification, this launches/resumes the app and the notification title, body and optional data payload is passed to the [onMessageReceived](#onMessageReceived) callback.

When the `onMessageReceived` is called in response to a user tapping a system notification while the app is in the background/not running, it will be passed the property `tap: "background"`.

Expand Down Expand Up @@ -420,6 +424,10 @@ Notifications on Android can be customised to specify the sound, icon, LED colou
#### Android background notifications
If the notification message arrives while the app is in the background/not running, it will be displayed as a system notification.

If a notification message arrives while the app is in the background but is still running (i.e. has not been task-killed) and the device is not in power-saving mode, the `onMessageReceived` callback will be invoked without the `tap` property, indicating the message was received without user interaction.

If the user then taps the system notification, the app will be brought to the foreground and `onMessageReceived` will be invoked **again**, this time with `tap: "background"` indicating that the user tapped the system notification while the app was in the background.

In addition to the title and body of the notification message, Android system notifications support specification of the following notification settings:
- [Icon](#android-notification-icons)
- [Sound](#android-notification-sound)
Expand Down Expand Up @@ -664,12 +672,19 @@ For example:
"payload": {
"aps": {
"sound": "default",
"badge": 1
"badge": 1,
"content-available": 1
}
}
}
}

#### iOS background notifications
If the app is in the background but is still running (i.e. has not been task-killed) and the device is not in power-saving mode, the `onMessageReceived` callback can be invoked when the message arrives without requiring user interaction (i.e. tapping the system notification).
To do this you must specify `"content-available": 1` in the `apns.payload.aps` section of the message payload - see the [Apple documentation](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW8) for more information.
When the message arrives, the `onMessageReceived` callback will be invoked without the `tap` property, indicating the message was received without user interaction.
If the user then taps the system notification, the app will be brought to the foreground and `onMessageReceived` will be invoked **again**, this time with `tap: "background"` indicating that the user tapped the system notification while the app was in the background.

#### iOS notification sound
You can specify custom sounds for notifications or play the device default notification sound.

Expand Down
35 changes: 28 additions & 7 deletions src/ios/AppDelegate+FirebasePlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ @interface AppDelegate () <UNUserNotificationCenterDelegate, FIRMessagingDelegat

@implementation AppDelegate (FirebasePlugin)

static NSDictionary* mutableUserInfo;

- (void)setDelegate:(id)delegate {
objc_setAssociatedObject(self, kDelegateKey, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
Expand Down Expand Up @@ -142,12 +144,14 @@ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(N
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {

@try{
NSDictionary *mutableUserInfo = [userInfo mutableCopy];
mutableUserInfo = [userInfo mutableCopy];
NSDictionary* aps = [mutableUserInfo objectForKey:@"aps"];
bool isContentAvailable = false;
if([aps objectForKey:@"alert"] != nil){
isContentAvailable = [[aps objectForKey:@"content-available"] isEqualToNumber:[NSNumber numberWithInt:1]];
[mutableUserInfo setValue:@"notification" forKey:@"messageType"];
NSString* tap;
if([self.applicationInBackground isEqual:[NSNumber numberWithBool:YES]]){
if([self.applicationInBackground isEqual:[NSNumber numberWithBool:YES]] && !isContentAvailable){
tap = @"background";
}
[mutableUserInfo setValue:tap forKey:@"tap"];
Expand All @@ -158,8 +162,14 @@ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(N
NSLog(@"didReceiveRemoteNotification: %@", mutableUserInfo);

completionHandler(UIBackgroundFetchResultNewData);
[self processMessageForForegroundNotification:mutableUserInfo];
[FirebasePlugin.firebasePlugin sendNotification:mutableUserInfo];
if([self.applicationInBackground isEqual:[NSNumber numberWithBool:YES]] && isContentAvailable){
[FirebasePlugin.firebasePlugin _logError:@"didReceiveRemoteNotification: omitting foreground notification as content-available:1 so system notification will be shown"];
}else{
[self processMessageForForegroundNotification:mutableUserInfo];
}
if([self.applicationInBackground isEqual:[NSNumber numberWithBool:YES]] || !isContentAvailable){
[FirebasePlugin.firebasePlugin sendNotification:mutableUserInfo];
}
}@catch (NSException *exception) {
[FirebasePlugin.firebasePlugin handlePluginExceptionWithoutContext:exception];
}
Expand Down Expand Up @@ -259,10 +269,14 @@ -(void)processMessageForForegroundNotification:(NSDictionary*)messageData {
[aps setValue:badge forKey:@"badge"];
}

NSString* messageType = @"data";
if([mutableUserInfo objectForKey:@"messageType"] != nil){
messageType = [mutableUserInfo objectForKey:@"messageType"];
}

NSDictionary* userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:
@"true", @"notification_foreground",
@"data", @"messageType",
messageType, @"messageType",
aps, @"aps"
, nil];

Expand Down Expand Up @@ -307,7 +321,8 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
}


NSDictionary* mutableUserInfo = [notification.request.content.userInfo mutableCopy];
mutableUserInfo = [notification.request.content.userInfo mutableCopy];

NSString* messageType = [mutableUserInfo objectForKey:@"messageType"];
if(![messageType isEqualToString:@"data"]){
[mutableUserInfo setValue:@"notification" forKey:@"messageType"];
Expand All @@ -317,6 +332,12 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
NSLog(@"willPresentNotification: %@", mutableUserInfo);

NSDictionary* aps = [mutableUserInfo objectForKey:@"aps"];
bool isContentAvailable = [[aps objectForKey:@"content-available"] isEqualToNumber:[NSNumber numberWithInt:1]];
if(isContentAvailable){
[FirebasePlugin.firebasePlugin _logError:@"willPresentNotification: aborting as content-available:1 so system notification will be shown"];
return;
}

bool showForegroundNotification = [mutableUserInfo objectForKey:@"notification_foreground"];
bool hasAlert = [aps objectForKey:@"alert"] != nil;
bool hasBadge = [aps objectForKey:@"badge"] != nil;
Expand Down Expand Up @@ -368,7 +389,7 @@ - (void) userNotificationCenter:(UNUserNotificationCenter *)center
return;
}

NSDictionary *mutableUserInfo = [response.notification.request.content.userInfo mutableCopy];
mutableUserInfo = [response.notification.request.content.userInfo mutableCopy];

NSString* tap;
if([self.applicationInBackground isEqual:[NSNumber numberWithBool:YES]]){
Expand Down

0 comments on commit cb3dda2

Please sign in to comment.