DEV Community

Cover image for Basics and caveats of Expo-notifications
Mariana Costa
Mariana Costa

Posted on • Edited on • Originally published at mariana-costa.web.app

Basics and caveats of Expo-notifications

Push notifications are currently a widely used functionality in mobile applications. They provide an easy way to establish communication with the users. I've recently started a journey to learn mobile development with React Native. In this post, I'll overview the basic implementation of push notifications using Expo-notifications, as well as some caveats that I had to overcome during the development.

Expo is a software development kit (SDK) that wraps a React Native application, simplifies the development environment setup and provides several utilities. One of this utilities is the Expo-notifications, which makes easier the implementation of push notifications. This package provides push notification tokens, and the ability to display, schedule, receive, interact and respond to notifications. Expo-notifications allows the implementation of 2 types of notification:

  • local notifications: notifications triggered by the app installed in a device and exclusively displayed in that device, they are never sent to other devices. This notification type is useful for remind notifications, for example.

  • push notifications: notifications remotely sent to the users; they are received by the app, triggering a local notification that is displayed to the user. These notifications are useful for in chat or banking applications, for example.

To send a push notification, you should send a POST request with a valid Expo push Token and the message to the Expo push notification service. Then, this service sends the notifications to Firebase Cloud Message (FCM) and Apple Push Notification Service (APNS), in case of android or ios operative systems, respectively, which send the message to the recipient devices. Be aware that expo-notifications does not work on emulators, so you should tested on real devices. You can use Expo Go to test your under development pp in your device.


For code implementations, there are three steps to consider:

  • Get Expo push token
  • Send notifications
  • Receive and manage notifications

Get Expo push token

Expo push token is an unique identifier of a certain device, allowing push servers to recognize it. Here is the code to get it:

import * as Notifications from 'expo-notifications'

const registerForPushNotificationsAsync = async () => {
  try {
    const { status: existingStatus } = await Notifications.getPermissionsAsync()
    let finalStatus = existingStatus
    if (existingStatus !== 'granted') {
      const { status } = await Notifications.requestPermissionsAsync()
      finalStatus = status
    }
    if (finalStatus !== 'granted') {
      throw new Error('Permission not granted!')
    }
    const token = (await Notifications.getExpoPushTokenAsync()).data
    return token
  } catch (error) {
    console.error(error)
  }
}
Enter fullscreen mode Exit fullscreen mode

Note that the code checks/asks for notifications permissions. This step is required for ios devices.


Send a notification

There are 3 ways to send push notifications with expo:

Using Expo's push notifications tool

This tool is very useful for testing purposes. To use it, go to Expo's push notifications tool, add the Expo push token from your app, fulfil the message fields and send the notification.

Sending a POST request to https://exp.host/--/api/v2/push/send

This POST request takes message content in request body. It can be sent from the app or from a server, using the fetch API or axios, for example.

fetch('https://exp.host/--/api/v2/push/send', {
  method: 'POST',
  headers: {
    Accept: 'application/json',
    'Accept-Encoding': 'gzip, deflate',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    to: 'expo-push-token',
    data: { extraData: 'Some data' },
    title: 'Sent via the app',
    body: 'This push notification was sent by to app!',
  }),
})
Enter fullscreen mode Exit fullscreen mode

From a backend server

Expo provides libraries that support sending push notifications for several programming languages. Here is an example using expo-server-sdk for Node.js:

const { Expo } = require('expo-server-sdk')
const expo = new Expo()

const sendPushNotification = async expoPushToken => {
  // Check that all your push tokens appear to be valid Expo push tokens
  if (!Expo.isExpoPushToken('expo-push-token')) {
    console.error(`expo-push-token is not a valid Expo push token`)
  }
  const messages = []
  const message = {
    to: 'expo-push-token',
    data: { extraData: 'Some data' },
    title: 'Sent by backend server',
    body: 'This push notification was sent by a backend server!',
  }
  messages.push(message)
  const chunks = expo.chunkPushNotifications(messages)
  const tickets = []

  try {
    ;(async () => {
      for (const chunk of chunks) {
        try {
          const ticketChunk = await expo.sendPushNotificationsAsync(chunk)
          tickets.push(...ticketChunk)
        } catch (error) {
          console.error(error)
        }
      }
    })()
  } catch (error) {
    console.error(error)
  }
}
Enter fullscreen mode Exit fullscreen mode

Manage received notifications

Expo-notifications allows to receive notifications when the app is in the foreground, background and killed. Be aware that you must define to receive notifications, when the app is in the foreground, as shown in the code below.
This package also allows to listen and run some code when a notification is received when the app is in the foreground and background, but not when the app is killed. addNotificationReceivedListener is useful to listen notifications received when the app is in the foreground, whereas TaskManager (imported from expo-task-manager) is useful to listen notifications received when the app is in the background. Here is an implementation example:

import * as Notifications from 'expo-notifications'
import * as TaskManager from 'expo-task-manager'
const BACKGROUND_NOTIFICATION_TASK = 'BACKGROUND-NOTIFICATION-TASK'

// defines how device should handle a notification when the app is running (foreground notifications)
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: false,
    shouldSetBadge: true,
  }),
})

const handleNewNotification = async notificationObject => {
  try {
    const newNotification = {
      id: notificationObject.messageId,
      date: notificationObject.sentTime,
      title: notificationObject.data.title,
      body: notificationObject.data.message,
      data: JSON.parse(notificationObject.data.body),
    }
    // add the code to do what you need with the received notification  and, e.g., set badge number on app icon
    console.log(newNotification)
    await Notifications.setBadgeCountAsync(1)
  } catch (error) {
    console.error(error)
  }
}

TaskManager.defineTask(
  BACKGROUND_NOTIFICATION_TASK,
  ({ data, error, executionInfo }) => handleNewNotification(data.notification)
)

useEffect(() => {
  // register task to run whenever is received while the app is in the background
  Notifications.registerTaskAsync(BACKGROUND_NOTIFICATION_TASK)

  // listener triggered whenever a notification is received while the app is in the foreground
  const foregroundReceivedNotificationSubscription = Notifications.addNotificationReceivedListener(
    notification => {
      handleNewNotification(notification.request.trigger.remoteMessage)
    }
  )

  return () => {
    // cleanup the listener and task registry
    foregroundReceivedNotificationSubscription.remove()
    Notifications.unregisterTaskAsync(BACKGROUND_NOTIFICATION_TASK)
  }
}, [])
Enter fullscreen mode Exit fullscreen mode

Manage user interaction with the notification

You can also implement some code run whenever a user interacts with/taps the received notification. The code below shows two different implementation approaches:

import * as Notifications from 'expo-notifications'

const Home = () => {
  // 1) using addNotificationResponseReceivedListener, which is triggered whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded, or killed
  useEffect(() => {
    const notificationInteractionSubscription = Notifications.addNotificationResponseReceivedListener(
      response => {
        // add the code to do what you need with the notification e.g. navigate to a specific screen
        handleNewNotification(response.notification, () =>
          navigation.navigate('NotificationList')
        )
      }
    )

    return () => {
      notificationInteractionSubscription.remove()
    }

    // 2) using useLastNotificationResponse
    const lastNotificationResponse = Notifications.useLastNotificationResponse()
    if (lastNotificationResponse) {
      add the code to do what you need with the notification e.g. navigate to a specific screen
      handleNewNotification(
        lastNotificationResponse.notification.request.trigger.remoteMessage,
        () => navigation.navigate('Notifications')
      )
    }
  }, [lastNotificationResponse])
}
Enter fullscreen mode Exit fullscreen mode

I tested both approaches but at the end I chose the second one because lastNotificationResponse returns the last notification the user interacted with. This overcomes the fact that addNotificationResponseReceivedListener in useEffect hook is called too late when app is launching (i.e. when the user interacted with a notifications received when the app was killed), leading to the "loss" of interaction listening in these cases.

Some points to pay attention

Here are some issues that I noticed and/or had to handle using Expo-notifications. If you also and managed them in a different way, please share with us.

  • Every time the app is installed, the device may get a new push token, which can lead to incorrect tokens saved in databases, for example. To ensure that the database always stores the correct token, considering that my app needs user authentication, I decided to store the push token in the database every time the user signs in, and delete it from every time the user signs out. This approach also prevents notifications reception when the user is not authenticated.

  • To receive the notification in the block screen, for android, it is important to set priority: "high" in the message object, otherwise the device wil not be "awaked" when a notification is received. Check also the device settings (Settings -> Notifications -> your-app-name), to ensure that all the permissions you need are given.

  • For android standalone apps, you must configure Firebase Cloud Messaging, as described here.

  • Currently, there is no way to react to a notification reception when the app is killed. For this reason, I was not able to add these notifications to the Notifications screen or increment the badge counter when there notifications are received

  • To navigate to a specific screen when a notification is pressed, I had to implement the listeners code in Home screen, in order to be able to use screen navigation provided by @react-navigation.

  • For android device, you can customize notification color and icon. The 96x96 icon should be white with transparent background. Be aware that, if you are using Expo Go, the custom icon and androidCollapsedTitle will not be displayed in develop mode, but they will work as expected in the standalone app. Here is an example of a configuration to customize the notifications, in app.json.

{
  "expo": {
    ...
    "plugins": [
      [
        "expo-notifications",
        {
          "icon": "../assets/notification-icon.png",
          "color": "#CA2C92",
          "sounds": [],
          "androidMode": "default",
          "androidCollapsedTitle": "your-app-name",
          "iosDisplayInForeground": true
        }
      ]
    ],
  }
}
Enter fullscreen mode Exit fullscreen mode

And that's all I had to share. If you want to check the implementation of push notification in the app I developed, you can do it here.

Hope to "see" you in my next post 👋.

Top comments (14)

Collapse
 
joaquinbian profile image
joaquinbian • Edited

Nice post! I have one question, if I have to send a notification like a reminder of an event, in a specific date, should I use local-notifications? And there is a way to send notifications when the app is killed?

Collapse
 
marianapatcosta profile image
Mariana Costa

Thank you for your comment.
Yes, the local notifications should be used for reminders. And yes, we can receive notifications when the app is killed but, as far as I know, there is no way to react to it, I mean there is no way add a listener and execute some code when a notification is received when the app is killed. Thus, we can receive and read a message, but we cannot increase the badge count when we receive a notification when the app is killed, for example.

Collapse
 
joaquinbian profile image
joaquinbian

Nice! Thanks for the answer! And I can i send these notifications event if they are local-notifications? Or I can receive only notifications from a server when app is killed?

Collapse
 
marianapatcosta profile image
Mariana Costa

Unfortunately, I did not test that but considering that the app triggers a local notification to display in the device when a push notification is received, I believe the behaviour is the same. Thus, we probably can still receive a local notification when the app is killed but we cannot react to it.

Collapse
 
gruckion profile image
Stephen Rayner

Interested to know how to tested this. So we need a real device and we can use the tool to send notifications. But is there a quicker way than uninstalling expo go to reset the app?

I like your idea of remove on logout. But that’s just delete from database, how do I reset to test as if from scratch each time?

Collapse
 
marianapatcosta profile image
Mariana Costa

Thank you for your comment.
I did not understand the first question. Why would you need to uninstall expo go? Nevertheless, to reset your app you don't need to uninstall expo go, you can just clear expo go cache, in "Applications" settings in your device.
Yes, the idea to redefined the token on login is to update the database and be sure that the correct token is being used. As far as I understand reading about expo-notifications package, every time the app is installed you get a new token. So, if you need to test from the scratch, I think it would be enough to uninstall and install the app again.

Collapse
 
adii9 profile image
Aditya Mathur

This is a very informative post. I just had one doubt, I see you are generating the expo push token using Notifications.getExpoPushTokenAsync() which will create a expo-push-token that can only be used when you are running your app using expo go app. Will the push token created by Notifications.getExpoPushTokenAsync() Can be used in physical devices as well?
According to my understanding, when you create the binaries for physical devices I think we need to use getDevicePushTokenAsync() to get a native FCM / APN token which can used with notification services.
You can find more detail here.

Collapse
 
marianapatcosta profile image
Mariana Costa

Hello, thanks for your message.
Yes, the token obtained by calling getExpoPushTokenAsync also works in a physical device, when a production-ready app is running. The article documents the process I went through to implement push notifications in an app I developed for personal usage. I am using it ocasionally and the push notifications work as expected. I am using expo-server-sdk to send notifications from a server.

Collapse
 
rop89 profile image
Rita(they/them) • Edited

Olá Mariana,
Obrigado pelo post.

I have a question though, how do you actually send the push notifications to a specific user? From my understanding this is just the build for sending "general notifications" to all app users but what about if I want to send a notification to a specific user? I am using React Native, mainly targetting IOS, do I need something like Firebase real time DB? Is there any example you can give for specific users notifications?

Desculpa por todas as perguntas, estou ha algum tempo a olhar para isto mas ainda nao consegui achar nada que me fizesse entender como fazer para alcançar o meu objectivo.

Thansk again

Collapse
 
adii9 profile image
Aditya Mathur

The expo notification token that will be generated for each user will be unique. So in your case i.e. you want to send the notification to a specific user, you can save the notification token for each user in the database and a the time of sending the push token you can provide the expo push token code of the target user in 'to' parameter:

body: JSON.stringify({
    to: 'Target-User-expo-push-token',
    data: { extraData: 'Some data' },
    title: 'Sent via the app',
    body: 'This push notification was sent by to app!',
  }),
Enter fullscreen mode Exit fullscreen mode
Collapse
 
caiomars profile image
Caio Mars

Great post! I have one question about sending notification errors, specifically DeviceNotRegistered error, how does one associate a receiptId to an expo token?

I'm sending notifications as chunks, for those users that have uninstalled the app I will get a DeviceNotRegistered error in my receipts (expo.getPushNotificationReceiptsAsync(chunk)) but I haven't figure out how to associate a receipt to a specific expo push token.

Thanks.

Collapse
 
marianapatcosta profile image
Mariana Costa

Thank you for your comment and sorry for the late response. I'm afraid I'll not be able to help you. Since the app I developed is for personal use only, I only implemented the logic to send a notification and generate a log in case an error occurs and it was not sent; I did not implement the logic to deal with the "receipt" of each notification. I've just read the docs(github.com/expo/expo-server-sdk-node) and some possibilities came into my mind, but probably you have already tested them: Did you checked all the information that is being sent in the "ticket" and in the "receipts" (expo.getPushNotificationReceiptsAsync(chunk))? Does the ticket have any information regarding the expo push token that we can relate to the recipient using its ID? Or has the 'details' field of recipient any useful information?

Collapse
 
psnehanshu profile image
Snehanshu Phukon

How to display a message notification with a reply option, similar to WhatsApp?

Collapse
 
dharmeshpathak profile image
dharmeshpathak

Hello guys! I want to know if we can style or customize the notification container in expo-notification ?