React Native push notifications with Firebase Cloud Messaging

Push notifications remain one of the main ways to interact with users and attract their attention when your application is not opened. Although sending a message to a user or group of users seems like a straightforward action, there are multiple nuances, conditions and challenges that require attention when setting up a push notification system. In this article we will cover some of the main challenges when setting up push notifications with a higher degree of functionality and interactivity.

Tools

In order to set up our push notification system we will utilize Firebase Cloud Messaging (FCM). As one of the most reliable notification services available it offers a layer of abstraction over the Apple’s Push Notifications Service (APNS) and Google’s individual APIs, taking care of the under-the-hood complexity. Although FCM supports displaying notifications with minimal integration, to power up its functionality we chose to use Notifee, a library to easily enrich notifications on React Native, it enables us to add actions, manage channels, trigger local notifications, hook into events, among other features. Last but not least we make use of React Native Firebase, it is the officially recommended collection of packages that brings React Native support for all Firebase services on both Android and iOS apps.

Setup

To make it easier to follow along, make sure you check out this sample repo. It will pair nicely to solidify concepts through real-world examples.

In the sample repo, you’ll find that firebase, and notifee have already been integrated. As you make your way through this post, you’ll be able to follow along with the codebase. The codebase has a few different branches to cover the topics we’ll touch on today.

Here’s a quick breakdown of how best to follow along:

  • main this branch is foundational, it has the initial integration with firebase
  • notifee: this branch is rather self-descriptive, integrates notifee
  • notifee-actions: this branch has an example implementation working with notifee actions
  • notifee-events: this branch has an example implementation working with notifee events

Prerequisites

Types of messages

The contents of a notification message determine its type and how it is going to be handled by the application and FCM.

Notification message

This is the standard notification type that we normally use, it contains a set of reserved keys to be used that determine the notification properties (e.g. title, body, etc). This type of message will be automatically handled by FCM and display the message in the notification tray of the client device.

Data-only message

Data-only messages are not handled automatically and require the application to manage its behavior based on the message properties. These messages can include any set of custom key-value pairs used by the application, reserved only a few special keys such as from, notification, message_type, or any word starting with google or gcm.

Notification with data payload

Standard notifications can also have a data payload, in this case each part will keep their standard behavior with the data payload only being handled when the application is awakened. If your application triggers notifications locally based on the data payload, be aware that there might be cases where duplicated notifications will be shown, one triggered by FCM’s handler and another one by your custom handler.

Foreground vs background

One of the main challenges of handling notifications are the different behaviors it has dependending on your application and device state. The Firebase Cloud Messaging documentation covers a few states, but those conditions may vary depending on different factors.

By default, foreground notifications won’t be displayed in the notifications tray on top of the screen and will be handled silently by the onMessage method inside your application code. There are a few different ways you can handle notifications in the foreground, such as:

  • Manually displaying the notification with a third-party tool
  • Silently handling the notifications to update a relevant state in the application
  • Displaying a customized alert within your application
  • Offering a deeplink to the relevant part of the application
  • Add notification to a dedicated list in a purpose notification view

Additionally, it is important to note that although the official documentation mentions that data-only notifications are not handled while the application is on the foreground, it is possible to silently handle it and manually display a notification.

Background notifications are displayed based on a different set of conditions such as the contents of the notification payload, which define its type (message, data-only, etc), priority of the notification, state of the application, battery, and data-usage settings. Standard messages are handled and displayed automatically on both Background and Quit states, on the other hand hand data-only notifications will be ignored as low priority in the same states, to force the application to handle them you can increase their priority at the sender side in Android with { "priority": "high" } and in iOS with {"content-available": 1 }.

Sending notifications

There are different ways of sending Push Notifications to users, from simple dashboard campaigns to more complex server calls that allow you to control multiple aspects of how a notification is delivered.

Dashboard

The simplest way of sending a notification to a user is by creating a campaign in your application’s Firebase Console. Campaigns allow you to target your notifications for specific groups of users and execute test runs for specific devices given a known identification token. To create a campaign execute the following steps:

  • On your Project shortcuts go to Engage > Messaging > New Campaign > Notifications
  • Add a Title and a Text used to fill the Header and Body of your notification card.
    • An image url can be added to be displayed when the card is expanded.
    • On this step it is possible to send a test notification to a known FCM token, this is useful for testing and debugging the notification flow.
  • Select one or multiple applications on your project as targets or send the notification to a specific topic, every device subscribed to the topic will receive the notification.
  • Schedule your notification as a one time message or recurring notification
  • Optionally, add analytics to measure conversion events or include extra options to be handled by your application. These options will be present in the data field of your notification object.
  • Review and Publish your campaign

REST or Firebase admin SDK

For complete control over the contents, conditions, targets, and behavior of your notifications, sending notifications through Firebase’s API will be the way to go. It gives you the ability to build your message from scratch, fully customizing its options, common fields, platform-specific behavior, additional data, among other features.

Initially, you need to authorize your requests to be able to send notifications from a trusted environment, follow the instructions provided in the documentation for your preferred method. After getting authorized you should be able to build your own notification send requests for different targets, devices, topics or groups. More details in the Build app server send requests documentation.

When building notifications using this method it is important to keep in mind the nuances of request configurations, as they have different structures across iOS and Android platforms. Android-specific behavior is configured through the android messages field following the AndroidConfig representation. Meanwhile, iOS notifications fundamentally go through the Apple Push Notification Service (APNs) requiring configurations to follow the request standard established by Apple for notifications, following the ApnsConfig representation in FCM.

Sending a simple notification

{
  "to": "/topics/all", // this can be a topic, group or token
  "notification": {
    "body": "This is a notification message!",
    "title": "FCM Message",
    "image": "https://firebase.google.com/images/social.png"
  }
}

This message will be handled by FCM built-in message handler and trigger a notification if the application is in the background. In the foreground, a notification is not displayed but it can still be silently handled.

Sending a data notification

{
  "to": "/topics/all", // this can be a topic, group or token
  "data": {
    "eventId": "1234",
    "dismiss": "true"
  }
}

This message will not display a notification by default, but can be handled within your application to update the application state, trigger background processes or update/destroy previous notifications. As mentioned above these will heavily rely on the device status and notification priority.

Sending a high-priority notification

{
  "to": "/topics/all",
  "data": {
    "eventId": "1234",
    "dismiss": "true"
  },
  "apns": {
    "headers": {
      "apns-priority": "10"
    },
    "payload": {
      "aps": {
        "content-available": 1
      }
    }
  },
  "android": {
    "priority": "high"
  }
}

The “to” field can be a topic, group or tokenThis message will set the notification priority to its highest level, signaling FCM to attempt to deliver the message immediately even if the application is in the background or a quit-state.

Handling actions

By default, users can only interact with notifications by pressing them to open the application or clearing them to ignore the message. Actions provide customizable ways for users to react to notifications hooking them to specific events in the application and trigger automatic behavior in the background.

notification with actions image

While FCM is able to support basic notification capabilities, advanced features such as actions require additional libraries to be implemented, FCM’s recommended tool to extend its features is Notifee, a library that enables us to build feature rich and more manageable notifications.

To add custom actions to your notification using Notifee it is necessary to set them up separately for each platform. For Android, add the action details to the `android` configuration field when calling the displayNotification method:

For iOS the action configuration is bound to the notification’s category being triggered and not the display configuration.

notifee.displayNotification({
  title: 'New Event',
  body: 'You have a new event in 10 Minutes',
  android: {
    channelId: 'messages',
    actions: [
      {
        title: 'Snooze 5min',
        pressAction: {
          id: 'snooze',
        },
      },
      {
        title: 'Join Now',
        pressAction: {
          id: join,
        },
      },
    ],
  },
});

To handle custom actions you can setup a listener and evaluate the possible action IDs (pressAction.id) in the event handler. We will discuss this in more details on the next section. The complete implementation for handling actions can be found in the notifee-actions branch at our repo.

Events

Events are triggered whenever the user interacts with notifications in any way, by pressing or dismissing it, pressing actions, clearing all notifications, etc. You can refer to all supported event types at the library documentation.

It is possible to handle events differently depending if the the app state is in the foreground or background, you can write separate handlers or assign a common function if the goal is to maintain a consistent behavior, but it is important to keep in mind that background handlers should be set as early as possible in the project and only a single one should be registered.

const handleEvent = async ({ type, detail }) => {
  if (type === EventType.ACTION_PRESS && detail.pressAction.id === 'snooze') {
    console.log(`User pressed the ${pressAction.title} action.`);
  }
};

notifee.onBackgroundEvent(handleEvent);

useEffect(() => {
  return notifee.onForegroundEvent(handleEvent);
}, [])

Android and iOS will diverge on how they handle events while the application is in the background or killed state. Android keeps a background service running in a thread that is capable of waking up the application and running its handler in the background even if the application was manually closed by the user, iOS however, will only handle actions silently if the application is kept in the background and has not been killed by the user (swiping up on the app icon in the application tray). It is worth mentioning that, in both cases, no UI updates can be triggered and only background tasks can be executed.

The complete implementation for handling events can be found in the notifee-actions branch at our repo.

Conclusion/TLDR

In conclusion, push notifications are a powerful tool for interacting with users, drawing and keeping their attention, but they also come with a number of challenges and nuances. We have utilized Firebase Cloud Messaging (FCM) as one of the most reliable notification services available, it offers a layer of abstraction over the Apple’s Push Notifications Service (APNS) and Google’s individual APIs, taking care of the under-the-hood complexity. To power up its functionality, we chose to use Notifee, a library to easily enrich notifications on React Native. It enabled us to add actions, manage channels, trigger local notifications, hook into events, among other features. We have also gone over different types of messages, such as Notification Message, Data-Only Message, Notification with Data Payload, and challenges when handling notifications in different states such as Foreground vs Background. Through this article, we have aimed to provide a comprehensive guide for developers to set up and manage push notifications in a React Native app, providing examples and a reference project that can be used as a starting point for more complex implementations.

Take your mobile app to the next level

When you work with thoughtbot, your team learns how to bring world class mobile experiences to any project. [Learn about our React Native services]](https://thoughtbot.com/services/react-native-development) and whether we’re a good fit for your project’s needs.