Skip to content
This repository has been archived by the owner on Sep 24, 2021. It is now read-only.

iOS issues to get push notifications with category work for FCM and Notifee #205

Closed
iandr01d opened this issue Dec 2, 2020 · 14 comments
Closed
Labels
Platform: iOS question Further information is requested

Comments

@iandr01d
Copy link

iandr01d commented Dec 2, 2020

Hi,

I have a couple of issues to address. Initially I let Notifee to handle the notification display for all foreground and background notifications on iOS (note: with actions) as follows:

notifee.onForegroundEvent(async ({type, detail}) => {
      handlePushNotificationActions(type, detail);
    });
  },

notifee.onBackgroundEvent(async ({type, detail}) => {
    handlePushNotificationActions(type, detail);
  });
 },

await notifee.setNotificationCategories([
  {
    id: "some_category_id",
    actions: [
      {
        id: "some_action_id",
        title: "some_action_title",
      },
    ],
  },
]);

const handlePushNotificationActions = (type, detail) => {
  const {notification, pressAction} = detail;
  if (
    type === EventType.ACTION_PRESS &&
    pressAction.id === "some_action_id"
  ) {
    // Handle action here
  }
};

Since I am using Notifee to display all the notifications, I send data-only payload from my Node server:

  token: fcmToken,
  data: {
    notifee: JSON.stringify({
      title: "Data Payload",
      body: "Some body",
      ios: {
        categoryId: "some_category_id"
      },
      data: {
        key: "value",
      },
    }),
  },
  apns: {
    payload: {
      aps: {
        "category": "some_category_id"
      },
    },
  },
};

However my app cannot receive any push notifications if the following 3 conditions are met:

  1. My iPhone is in low power mode
  2. My phone is unplugged (not charging)
  3. The app is killed

I tried adding contentAvailable: true to the payload but I still did not receive any push notification.

{
  token: fcmToken,
  data: {
    notifee: JSON.stringify({
      title: "Data Payload",
      body: "Some body",
      ios: {
        categoryId: "some_category_id",
      },
      data: {
        key: "value",
      },
    }),
  },
  apns: {
    headers: {
      "apns-priority": "10",
    },
    payload: {
      aps: { 
        contentAvailable: true
       },
    },
  },

To be able to receive push notification in above 3 conditions, I had to add the notification payload when sending push from server:

{
  token: fcmToken,
  notification: {
    title: "Notification Payload",
    body: "Some body",
  },
  data: {
    notifee: JSON.stringify({
      title: "Data Payload",
      body: "Some body",
      ios: {
        categoryId: "some_category_id"
      },
      data: {
        key: "value",
      },
    }),
  },
  apns: {
    payload: {
      aps: {
        "category": "some_category_id"
      },
    },
  },
};

This causes another issue, now both the FCM and Notifee libraries show two notifications (one each) if the app is in the background (not killed).

If the app is killed, another issue arises - by adding the notification payload when sending push from server, Notifee's onBackgroundEvent will not be called anymore. It looks like the FCM library is handling the push notification instead of Notifee. As I mentioned earlier I need to create action button for my push notification. I then add "category": "some_category_id" in the apns payload. The push notification received is now showing the action but I do not know how can I handle the callback when the action button is clicked?

The Firebase messaging library has this callback when the push notification is tapped:
messaging().onNotificationOpenedApp((remoteMessage) => { })

However I need to handle the click action callback. There is no way for me to get the pressAction type like the notifee.onBackgroundEventcallback.

Any idea on how to fix these issues?

  1. Data-only push will not show if iPhone is in low power mode, unplugged, and app is killed
  2. Data+Notification push will show notification twice if app is in background but not killed
  3. I am unable to handle click actions with Data+Notification push if app is in background or killed since notification display is handled by FCM library.

I think if we're able to fix issue 1, we won't have to deal with 2 and 3. Thanks!

@mikehardy
Copy link
Collaborator

I believe it is 'content-available: 1' ?

@mikehardy
Copy link
Collaborator

(sorry to be so brief! It's 2020 and I'm also in virtual school with my kid helping 😅 - but that may break through most/all of the problems here)

@iandr01d
Copy link
Author

iandr01d commented Dec 2, 2020

No worries! 😄 Thanks for your quick reply! I just tried 'content-available: 1' and it didn't work either. Everything works if I get out of low power mode though.

@iandr01d
Copy link
Author

iandr01d commented Dec 2, 2020

If it helps, when I send both notification+data payloads from server:

  1. If my phone is in low power mode, I only receive one push notification (the one processed by FCM on notification payload)
  2. If my phone is not in low power mode, I receive two push notifications - one processed by FCM (notification payload) and another one processed by Notifee (data payload)

Looks like the data payload is ignored completely in low power mode.

@mikehardy
Copy link
Collaborator

mikehardy commented Dec 2, 2020

I believe that is correct, low-power mode means data-only is not ignored but it is queued. You should receive the message when the phone has full power again or if the user opens the app

Mixed payloads mean that you will get firebase-ios-sdk processing the notification via some hooks where your code never gets control in order to show the notification, and then either when the user taps on the notification or (if fully powered up with regular delivery I suppose) you will also have your data handler called.

I don't like the iffy-ness of mixed payloads since I'm not in 100% control of notification display but I have easier constraints on message delivery, they can wait until the user is not low power or in the app. If you have tighter constraints doing a mixed payload but knowing that for those mixed payload JSON you should perhaps do something with state internally but not show a notification might work for you.

Note that you are never receiving two JSON chunks from the cloud. I believe you are just seeing different handlers called under different situations, where apple is assuming that in low-power mode a data-only notification isn't worth spending power on.

@helenaford helenaford added Platform: iOS question Further information is requested labels Dec 8, 2020
@ccfz
Copy link

ccfz commented Dec 10, 2020

I have been playing around with this for 3 weeks now and agree that content-available: 1 is not enough. This is the request I would assume should work:

message: {
  data: {
    body: message,
    type: message_type
  },
  apns: {
    payload: {
      aps: {
        "content-available": 1
      }
    }
  },
  token: device.token
}

@khiawyen I agree that the notification payload is the only method that seems to work consistently. To avoid your problem of duplicate notifications use apns directly. This can be achieved by using the apns > payload > aps > alert key. For a detailed explanation of what customizations are available have a look at the apple push notification documentation. Here is my above request using apns directly:

(snake case because it is ruby)

message: {
  data: { # this can be used for forground message handling and for android in general
    body: message,
    type: message_type
  },
  apns: {
    payload: {
      aps: {
        category: "custom_category", # This will work with categories that were defined somehow. I use notifee for that.
        alert: { # overwrites the data payload in case of iOS
          body: message
        }
      }
    }
  },
  token: device.token
}

We are not live yet, so I only tested this in a dev environment.

Based on the apple documentation regarding background app update it appears that aside from content-available it is also important to set the headers: apns-priority and apns-push-type. I tried that but it made no difference. Bottom line I don't think this is really a bug from firebase or apns but expected behavior from apns, the docs state:

When a device receives a background notification, the system may hold and delay the delivery of the notification, which can have the following side effects:

When the system receives a new background notification, it discards the older notification and only holds the newest one.

If something force quits or kills the app, the system discards the held notification.

If the user launches the app, the system immediately delivers the held notification.
...
Your app has 30 seconds to perform any tasks and call the provided completion handler

For me this means that the whole data notification concept on iOS is relatively brittle and is not a viable option for 100% notification delivery.

@mikehardy
Copy link
Collaborator

That is correct. Data only is 'best effort' only. It should be used for refresh type things that speed up the experience on launch like pre cache, or for work that may be missed without problem.

@ccfz
Copy link

ccfz commented Dec 10, 2020

@mikehardy it does not actually really mention this anywhere on notifee or react-native-firebase. A lot of issues regarding background notification for iOS that you commented on I think might benefit from this information, in addition to the docs i.e. if you want reliable notifications don't use data, use apns alerts. At the moment the general notion I got from all the issues was: if you want iOS notification with notifiee then add content-available.

@mikehardy
Copy link
Collaborator

indeed - the platforms have evolved a bit underneath us, as well as my/our understanding of it. All the docs pages have edit buttons top right, anywhere you can think of that it would be useful if you want to submit a PR that could help a lot. Invertase - same team behind this module, react-native-firebase and FlutterFire (new work for us/them) have added this here https://firebase.flutter.dev/docs/messaging/usage/#low-priority-messages

it is probably the best summary of current knowledge (as it is the freshest chunk of docs written on the topic)

@helenaford
Copy link
Member

Hi @khiawyen, @ccfz thanks for all the info you provided on this issue around background notification on iOS. It's now possible to send a firebase message with categories, and receive ACTION_PRESS events using Notifee's Notification Service Extension Helper (https://notifee.app/react-native/docs/ios/remote-notification-support). This should fix this issue, but if it doesn't please let us know and we can re-open this.

@fabyeah
Copy link

fabyeah commented May 12, 2021

@helenaford @mikehardy I use data only notifications on android and create a notifee notification from that, as I need to handle EventType.PRESS in notifee.onBackgroundEvent. But if I drop the data and switch to the format as shown in your link example:

{
    notification: {
      title: 'A notification title!',
      body: 'A notification body',
    },
    apns: {
        payload: {
            aps: {
                'content-available': 1, // Important, to receive `onMessage` event in the foreground when message is incoming
                'mutable-content': 1, // Important, without this the extension won't fire
            },
            notifee_options: {
                image: 'https://placeimg.com/640/480/any', // URL to pointing to a remote image
                ios: {
                    sound: 'media/kick.wav', // A local sound file you have inside your app's bundle
                    categoryId: 'post', // A category that's already been created by your app
                    ... // any other api properties for NotificationIOS
                },
            },
        },
    },
    ...
};

to avoid the problems with data-only notifications on iOS, won't there be two notifications now shown on android? One from fcm and one from notifee? If I don't create a notifee notification, how do i get the press event from the fcm notification?

@ccfz
Copy link

ccfz commented May 17, 2021

Hey @fabyeah check the below example. This works on android and ios no matter the app status. I know I am not giving much explanation so if this is not self-explanatory then I will elaborate.

{
  android: {
    priority: "high",
    data: {
      channel_id: message_type,
      body: message
    }
  },
  apns: {
    payload: {
      aps: {
        alert: {
          body: message
        },
        category: message_type
      }
    }
  },
  token: device_token
}

@fabyeah
Copy link

fabyeah commented May 23, 2021

@ccfz thanks for the example. I had some issues sending notifications with Postman, as I was using the legacy HTTP API (https://firebase.google.com/docs/cloud-messaging/migrate-v1). Now, using the new HTTPv1 API, I can define data separately for android and iOS, as shown in your example. In the client I've had to separate some code with platform checks, but overall it's working now.

@helenaford
Copy link
Member

@ccfz thanks for responding and sharing your solution 😎 Definitely something we should add to the docs as I think it's a common issue.

@fabyeah great to see you have it working. I'm sorry I didn't reply to your first comment, if you open up a new git issue if anything crops up again, I should get notified. Not sure why I missed it as you tagged me 🤔

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Platform: iOS question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants