In Part 1 of the Push Notifications series, we announced our Scala library for sending and processing Expo push notifications. Along with the backend changes, there are a few more changes you will need in order to send push notifications with Expo. Specifically, your mobile application needs to support registering for notifications and receiving notifications.
As mentioned briefly in part 1, before you can send a push notification you’ll need a recipient. Just like sending a letter, an address is crucial. Fortunately, a mobile device can ask Expo for its address, which is called an Expo push token. Persist that token on your server so you can send a notification later when it’s appropriate.
Additionally, there are a few actions you may want to take on your phone once it receives a push notification – specifically, any actions that should occur when the user clicks on the notification.
Registering for notifications
Registering an Expo push token is simple. First, I define a graphQL mutation that will tell our server to associate this Expo push token with the current user of the app. The graphQL mutation is not shown but it should look like any mutation endpoint that you have on your system.
After the mutation exists server-side, I use a React ‘useEffect’ to contain the registration logic. This useEffect triggers whenever the user id changes, as well as whenever the app is brought into the foreground of the device. Registering on user id change will register on user login. Registering on app foregrounding covers the case where a user grants the app permission to send push notifications after logging in.
You can see more information about the AppState change listener here.
As you may be able to tell from the code, I went with a “shotgun” approach to registering an Expo push token. It is a low-cost action, and there is no harm to registering a token twice on our backend (it is idempotent), so a few redundant calls are okay.
Not shown: a similar version of this code that removes the token on logout.
Once you’ve sent your notification, make sure your app is able to properly handle it. For example, you may want the app to take a specific action when the user selects the notification. This is done by registering a listener, then handling the incoming notification object in your listener.
I omitted the implementation of navigateOnNotification, as that logic will be specific to your own application and the data you send with the notification. (Check out the section below, "SDK Versions before 38," for more details on the difference between Android and iOS implementation in this method.)
There are a few additional considerations. Software is always full of unexpected behaviors and edge cases; here are a few that we ran into implementing push notifications at Merit. Hopefully, it will save you a headache.
Something we’ve noticed when processing our notifications is that a push ticket will occasionally fail to have an associated push receipt. If you run a regularly scheduled job to process your sent push notifications, you may end up processing a push ticket immediately after it was created. To avoid this issue, I recommend only processing tickets that have existed for some minimum duration of time, around two to five minutes.
SDK Versions before 38
While I believe this bug has been fixed in the recent Expo SDK 38 release, bear in mind that if you are using an older version of Expo, then there is a bug in the notification listener on Apple devices. Specifically, the origin field of the notification event is sometimes “received” even though the user has selected the notification and the event origin field should be “selected.” You can see my work-around and comment detailing the behavior in the code snippet in the Receiving Notifications section of this post.
Only ask for permissions once
If you use an Apple device like an iPhone or iPad, you may have noticed that most major apps with push notifications ask for permission to send notifications before they officially ask for notification permissions. That is, they ask within the app before they formally get iOS to ask you for permission.
This is because iOS restricts apps to only one request for push notification permissions. So if you ask and the user denies push notification permission, then the only way the app will gain permission is if the user rummages through the menus and grants the permission by hand. To avoid being rejected most apps do a soft ask first, and only if the user indicates they want to receive push notifications does the app actually request them.
Of course, this works differently on Android devices where you are allowed multiple permission requests. But for iOS users, you may want to implement a two-pass permission check.
Apps with logins
If users log in to your app, then you probably want to send notifications appropriate for whoever is currently authenticated to the app. Some scenarios to consider:
What if two users share a phone and regularly login with different credentials?
If your server times out an authenticated session, should the push token be removed?
Is it okay to have sensitive information displayed on the push notification, knowing that it may pop up on the screen at any time?
Multiple Expo tokens per user
If your user has multiple devices, do you want to send the same push notification to all their devices? That is what we are doing at Merit. However, you may want to only keep one Expo push token per user.
I hope this helps you implement push notifications for your app. This is the first initiative I ran as a Merit employee and it was a great experience. Merit empowers its employees with the ability to make important engineering decisions. Any developer can be an initiative owner at Merit and I really enjoyed working with the Product and Data Science teams to put it all together.