React-native-push-notification: 'onNotification' not fired in background - (iOS)

Created on 22 Feb 2016  路  16Comments  路  Source: zo0r/react-native-push-notification

I'm using OneSignal to send push notifications to my react-native app. On iOS, everything works really well when the app is in the foreground. When the app is in the background, I see the notification on the lockscreen but my onNotification is not fired until I open the app (tried it both with the screen on and the screen off)

Haven't tested this yet on Android

Most helpful comment

Ok, its a bit confusing, but I have both android and iOS notifications working. Here's what I discovered:

There are 3 types of notification both both iOS and Android. The first type, and the least interesting I guess is the sort that doesn't make it into your app. They just appear in the notification centre, they can make a noise, vibrate the device and display some text. The android ones can be quite sophisticated. But, basically they look like this (not all possible fields are represented):

iOS:

{
  "aps":{
    "badge":8,
    "alert":"You have 8 unread messages",
    "sound":"default"
  }
}

Android:

{
  "to":"token",
  "time_to_live":86400,
  "collapse_key":"new_message",
  "delay_while_idle":true,
  "notification":{
    "title":"some title",
    "body":"You have 5 unread messages",
    "tag":"new_message",
    "icon":"new_message",
    "color":"#18d821",
    "sound":"default"
  }
}

The 2nd type are really ones we're interested in which are the notifications that make their way into you app. They don't cause any vibrations or make any sound. Now, I believe you can combine the 2 but don't bother, for reasons I'll explain below. These look like:

iOS:

{
  payload: {"yourField":"goes here"}, 
  aps: {"sound": "default", "category": "cat", "content-available": 1}
}

Note: "content-available": 1 is very important here.

Android

{
  "to":"token",
  "time_to_live":86400,
  "collapse_key":"new_message",
  "delay_while_idle":true,
  "data":{
    "payload":{
      "yourField":"goes here"
    }
  }
}

Note: the absence of a notification field is intentional.

To get hold of the data in the _payload_ in my onNotification method in RN I do something along the lines of:

      let payload;
      if (Platform.OS === 'android' && notification.payload) {
        payload = JSON.parse(notification.payload)
      } else if (notification.data && notification.data.payload) {
        payload = JSON.parse(notification.data.payload)
      }

The 3rd and final type is the internal notification, basically your RN app sending a notification to your own device. Now my reason for saying don't combine type 1 and 2 is that it's easier and I think better to send an internal notification in response the receiving a type 2 message. If you use an internal notification you have the highest control of what they look like. You can specify sounds, colours, vibrations, message body, message title, numbers etc (again, the android ones can be quite sophisticated but this API doesn't support it all yet). So when you receive a type 2 notification, in your onNotification callback you can then alert the user (if you like) with something noisy and vibraty, like:

PushNotification.localNotification({
        title: "this is at the top",
        autoCancel: true,
        icon: "new_message",
        vibrate: true,
        vibration: 300,
        subText: "this is at the bottom",
        color: "#18d821",
        id: 22,
        tag: "some_tag",
        group: "group",

        /* iOS and Android properties */
        message: "hello! something really cool is going on",
        number: 99,
        playSound: true
      });

(Again, not all possible fields are shown in this example)

Note: the message field is super important here and is a _special_ field used by this api to populate the text of the internal notification.

All 16 comments

Actually the same thing happens with react native's own PushNotificationsIOS. For those who are interested, If you want to perform work in the application, you need to include a content-available flag in the push notification and set your app to receive them as described here: https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html#//apple_ref/doc/uid/TP40007072-CH4-SW57

Also, you're going to need to implement - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler as described here https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/index.html#//apple_ref/occ/intfm/UIApplicationDelegate/application:didReceiveRemoteNotification:fetchCompletionHandler:

make sure you call the completion handler. if iOS detects that the completion handler was not retained or was never called, your app wont be launched.

Do you have an example of what an implementation of - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler would look like? I've tried the following but I don't receive the callback inside mr RN code:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
{
  [RCTPushNotificationManager didReceiveRemoteNotification:notification];
}

// Required for silent notification event (with "content-available" = 1) which *should* wake up the app in the background for a few seconds
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  NSLog(@"notification-body => %@", notification);
  [RCTPushNotificationManager didReceiveRemoteNotification:notification];
  completionHandler(UIBackgroundFetchResultNewData);
}

@npomfret

If you want to do anything in react upon receiving a background fetch you'll need to retain the completion handler and then call it once your work is done. Calling the completion handler right away causes the app to go straight back into the background.

See this thread: https://github.com/facebook/react-native/issues/1282#issuecomment-189201873

Here's an example of my implementation:

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {

  [RCTPushNotificationManager didReceiveRemoteNotification:userInfo];

  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    completionHandler(UIBackgroundFetchResultNewData);
  });
}

@yonahforst could u add all this great info to README.md please?

I will do - just I have an outstanding PR waiting already. Can't get anyone to merge it though.

@zo0r could u take a look on @npomfret outstanding PR? or assign anybody else? or add him as a contributor please

:wave: @yonahforst I'm having similar issues with Onesignal 馃槃 did you install the onesignal SDK as well?

@peterpme - that's bananas, I have your "custom fonts checklist" open on my screen right now. No, I'm using their rest api instead. Here are my helper classes, if they are any use to you: https://gist.github.com/yonahforst/5d01c90c9b7769ae5bdf3ded3444139e

@yonahforst small world :smile: Thanks a ton! I'm going to look this over, I appreciate it!

@yonahforst I hate to bother you again, but did you have to update your onesignal payload to support "alert" instead of "message"? I think that's what it's coming down for me

@peterpme what do you mean by 'update my onesignal payload'? I remember there being some discrepancy between a received field name and the expected field name, but now that I look back at it, that was with Parse, not Onesignal

@yonahforst so I'm looking through RNPushNotificationHelper.java right now and on line 99 I see this:

        if (bundle.getString("message") == null) {
            return;
        }

My notification payload returns an object with the following keys:

collapse_key,
custom: stringified JSON that includes an object:  `a: { message, i}`
foreground,
title

The reason I'm asking is because I get a notification when I'm in foreground, but nothing happens in the background.

I removed the onesignal SDK both ios / android

Thanks again!

ah, android! I haven't gotten to implementing push notification on Android yet. Should get there in the next few days.

Ok, its a bit confusing, but I have both android and iOS notifications working. Here's what I discovered:

There are 3 types of notification both both iOS and Android. The first type, and the least interesting I guess is the sort that doesn't make it into your app. They just appear in the notification centre, they can make a noise, vibrate the device and display some text. The android ones can be quite sophisticated. But, basically they look like this (not all possible fields are represented):

iOS:

{
  "aps":{
    "badge":8,
    "alert":"You have 8 unread messages",
    "sound":"default"
  }
}

Android:

{
  "to":"token",
  "time_to_live":86400,
  "collapse_key":"new_message",
  "delay_while_idle":true,
  "notification":{
    "title":"some title",
    "body":"You have 5 unread messages",
    "tag":"new_message",
    "icon":"new_message",
    "color":"#18d821",
    "sound":"default"
  }
}

The 2nd type are really ones we're interested in which are the notifications that make their way into you app. They don't cause any vibrations or make any sound. Now, I believe you can combine the 2 but don't bother, for reasons I'll explain below. These look like:

iOS:

{
  payload: {"yourField":"goes here"}, 
  aps: {"sound": "default", "category": "cat", "content-available": 1}
}

Note: "content-available": 1 is very important here.

Android

{
  "to":"token",
  "time_to_live":86400,
  "collapse_key":"new_message",
  "delay_while_idle":true,
  "data":{
    "payload":{
      "yourField":"goes here"
    }
  }
}

Note: the absence of a notification field is intentional.

To get hold of the data in the _payload_ in my onNotification method in RN I do something along the lines of:

      let payload;
      if (Platform.OS === 'android' && notification.payload) {
        payload = JSON.parse(notification.payload)
      } else if (notification.data && notification.data.payload) {
        payload = JSON.parse(notification.data.payload)
      }

The 3rd and final type is the internal notification, basically your RN app sending a notification to your own device. Now my reason for saying don't combine type 1 and 2 is that it's easier and I think better to send an internal notification in response the receiving a type 2 message. If you use an internal notification you have the highest control of what they look like. You can specify sounds, colours, vibrations, message body, message title, numbers etc (again, the android ones can be quite sophisticated but this API doesn't support it all yet). So when you receive a type 2 notification, in your onNotification callback you can then alert the user (if you like) with something noisy and vibraty, like:

PushNotification.localNotification({
        title: "this is at the top",
        autoCancel: true,
        icon: "new_message",
        vibrate: true,
        vibration: 300,
        subText: "this is at the bottom",
        color: "#18d821",
        id: 22,
        tag: "some_tag",
        group: "group",

        /* iOS and Android properties */
        message: "hello! something really cool is going on",
        number: 99,
        playSound: true
      });

(Again, not all possible fields are shown in this example)

Note: the message field is super important here and is a _special_ field used by this api to populate the text of the internal notification.

@yonahforst this solution works well if the app is backgrounded, but I've noticed after a couple of hours that it seem to stop working. I think what's happening is that the OS has suspended the app, and after this happen the behaviour changes. The push notification is still being delivered but my app doesn't seem to be given enough time to do anything in respond to it. Have you noticed the same?

I found this which suggests not to use dispatch_after but to use dispatch_async instead.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sungjinoh picture sungjinoh  路  3Comments

Benzer1406 picture Benzer1406  路  3Comments

NiuQiaoling picture NiuQiaoling  路  3Comments

GastonEDiaz picture GastonEDiaz  路  3Comments

DaniShalash picture DaniShalash  路  3Comments