send and receive notifications on Expo for React Native

Jun 27, 2023

on client side, you need to install expo-notifications, one of the utilities provided by Expo that allows,

  • sending notifications to other devices, as well as
  • displaying notifications on a device that are triggered by an app installed on that device itself (think reminders, alarms etc.)

sending notifications to remote devices

in order to send a notification to another device, we need an Expo push token generated on that device which acts as a unique identifier for it.

// function that requests/checks for permission for the app to send notifications,
// and then generates an Expo push token and returns it
async function generatePushTokensAsync() {
  // android 13 needs at least one notification channel created before it shows the permission prompt
  // so before getExpoPushTokenAsync() we need to call setNotificationChannelAsync()
  if (Platform.OS === 'android') {
    await Notifications.setNotificationChannelAsync('default', {
      name: 'default',
      importance: Notifications.AndroidImportance.HIGH
    });
  }

  let permissionStatus;

  const { status: existingPermissionStatus } = await Notifications.getPermissionsAsync();

  permissionStatus = existingPermissionStatus;

  if (existingPermissionStatus !== 'granted') {
    const { status } = await Notifications.requestPermissionsAsync();
    permissionStatus = status;
  }

  if (permissionStatus !== 'granted') {
    // handle notications not being allowed
  }

  const token = (await Notifications.getExpoPushTokenAsync()).data;

  return token;
}

to make push tokens of other users available anytime for an app, an example of an approach would be to generate and store the push token on the app database every time users logs in to the app.

this also helps to make sure the stored push token for a user is always correct, because on android devices when an app is uninstalled and reinstalled the push token changes.

Expo provides two ways to send notifications to remote devices: either by sending a POST request to https://exp.host/--/api/v2/push/send or from server-side using one of the backend libraries for several languages.

using http

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: pushToken,
    data: { some: data },
    title: 'notification title',
    body: 'notification body',
  }),
})

using a backend library - expo-server-sdk for node

import { Expo } from 'expo-server-sdk';

// optionally providing an access token if push security enabled
let expo = new Expo({ accessToken: process.env.EXPO_ACCESS_TOKEN });

for (let pushToken of somePushTokens) {
  messages.push({
    to: pushToken,
    title: 'notification title',
    body: 'notification body',
    data: { some: data },
  })
}

// expo recommends that you batch your notifications to reduce the number of requests 
// and to compress them
let chunks = expo.chunkPushNotifications(messages);

let tickets = [];

(async () => {
  for (let chunk of chunks) {
    try {
      let ticketChunk = await expo.sendPushNotificationsAsync(chunk);

      tickets.push(...ticketChunk);
    } catch (error) {
      console.error(error);
    }
  }
})();

// ...

// make sure to check for errors in tickets and receipts
// as they may contain error codes you need to handle
// refer to the documentation for more - https://github.com/expo/expo-server-sdk-node

triggering local notifications

import * as Notifications from 'expo-notifications';

Notifications.scheduleNotificationAsync({
  content: {
    title: 'notification title',
    body: 'notification body',
  },
  trigger: {
    seconds: 1,
  },
});

refer to the documentaion to learn how to implement schedulable, time-based local notifications by utilizing the trigger property above.

receiving notifications

while the user will receive notifications when the app is backgrounded or killed, while the app is in the foreground, we have to use setNotificationHandler to set a callback that will handle notification behaviour.

the following will always show notifications to the user when they are received if the app happens to be running.

import * as Notifications from 'expo-notifications';

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: false,
    shouldSetBadge: false,
  }),
});

event listeners

to listen to both notifications and user responses and add behaviour, we can use the event listeners provided with expo-notifications.

const notificationListener = useRef();
const responseListener = useRef();

useEffect(() => {
  notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
    console.log(notification);
  });

  responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
    console.log(response);
  });

  return () => {
    Notifications.removeNotificationSubscription(notificationListener.current);
    Notifications.removeNotificationSubscription(responseListener.current);
  };
}, []);