Research collaborate build

Oct 22, 2020

Notifications on React Native using Firebase Cloud Messaging with Notifee.

Basic implementation of Firebase messaging and Notifee on React Native
Neeraj Kumar Sinha
Neeraj Kumar SinhaAssociate Engineering Manager
lines

Firebase Cloud Messaging

Firebase Cloud Messaging is a notification service which can be used to send push notifications and in-app messages in iOS and Android applications.It supports web as well.

Key capabilities of Firebase Cloud Messaging

  • Send notification messages or data messages:
    Send notification messages that are displayed to your user or send data messages and determine completely what happens in your application code. See Message types.
  • Versatile message targeting:
    Distribute messages to your client app in any of 3 ways — to single devices, to groups of devices or to devices subscribed to topics.
  • Send messages from client apps:
    Send acknowledgments, chats and other messages from devices back to your server over FCM’s reliable and battery-efficient connection channel.

How does FCM work?

An FCM implementation includes two main components for sending and receiving:

  1. A trusted environment such as Cloud Functions for Firebase or an app server on which to build, target and send messages.
  2. An iOS, Android, or web (JavaScript) client app that receives messages via the corresponding platform-specific transport service.

You can send messages via the Firebase Admin SDK or the FCM server protocols. For testing or for sending marketing or engagement messages with powerful built-in targeting and analytics, you can also use the Notifications composer.

Here is a brief illustrative overview of the FCM Architecture:

Firebase Cloud Messaging architectural description

See the architectural overview for more detail and important information about the components of FCM.

Installation with React Native

To use FCM with React Native, first you need to add react-native-firebase to your project.

Installation with NPM:
Install the React Native Firebase 'app' module to the root of your React Native project with NPM or Yarn:

# Install & setup the app module
yarn add @react-native-firebase/app

# Install the messaging module
yarn add @react-native-firebase/messaging

# If you're developing your app using iOS, run this command
cd ios/ && pod install

Follow the further installation instruction in detail here: https://rnfirebase.io/

Usage:

iOS - Requesting Permissions

iOS prevents messages containing notification (or ‘alert’) payloads from being displayed unless you have received explicit permission from the user.

To learn more about local notifications, view the Notifications documentation.

The @react-native-firebase/messaging module provides a requestPermission method which triggers a native permission dialog requesting the user's permission:



import messaging from '@react-native-firebase/messaging';
async function requestUserPermission() {
  const authStatus = await messaging().requestPermission();
  const enabled =
    authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
    authStatus === messaging.AuthorizationStatus.PROVISIONAL;
  if (enabled) {
    console.log('Authorization status:', authStatus);
  }
}

The permissions API for iOS provides much more fine-grain control over permissions and how they’re handled within your application. To learn more, view the advanced iOS Permissions documentation.

On Android, you do not need to request user permission. This method can still be called on Android devices; however, iand will always resolve successfully.

FCM messages are sent to the registered devices, I would recommend testing on real device however messages can be sent to Android simulators.

To understand how the FCM messages are received, we would first have to know the application states in which application receives the message and behave accordingly.

The app can either be in Foreground state or in Background when the app is minimized, or it can be in quit state when the user has killed the app.

The app must have to be opened once before notifications can be received. Also instead of closing the app, if user force quits the app then the user must reopen the app to start receiving notifications again.

When the message is fired from the Firebase admin SDK or from console, it is received in one of the following handlers depending on the app state.

OnMessage: when the app is in foreground state.
SetBackgroundMessageHandler: When the app is in background or quit state.

An FCM message consists of two payloads:

Notification: A push notification will be displayed by the app if this field is included (does not display a notification when the app is active in the foreground).
Data: onMessage handler will catch the events if this field is included and the app is active in the foreground.

To learn more about how to send these options in your message payload, view the Firebase documentation for your FCM API implementation.

Foreground State Messages

To listen to messages on foreground, we will use the onMessage handler

import React, { useEffect } from 'react';
import messaging from '@react-native-firebase/messaging';

function App() {
  useEffect(() => {
    const unsubscribe = messaging().onMessage(async remoteMessage => {
   console.log("new message" , remoteMessage);
    // show the message to the user or use it to update to your local store
   return unsubscribe;
  }, []);
}

Background/Quit State Messages

To receive notifications when the app is not active we would have to configure a background handler to receive messages.

// index.js
import { AppRegistry } from 'react-native';
import messaging from '@react-native-firebase/messaging';
import App from './App';

// Register background handler
messaging().setBackgroundMessageHandler(async remoteMessage => {
  console.log('Message handled in the background!', remoteMessage);
});

AppRegistry.registerComponent('app', () => App);

If notification property was sent to the remoteMessage, the notification would have already been shown by the FCM SDK.

Data Only Messages:

When a notification only has data in the payload and does not have the notification property, it can be referred to as 'data only' messages or 'silent' messages.

These silent notifications can be useful when you want to update the local store of the user in the background or show the notifications yourself (using third party notification handlers in React Native such as Notifee).

When an incoming message is 'data-only' (contains no notification option), both Android & iOS regard it as low priority and will prevent the application from waking (ignoring the message). To allow data-only messages to trigger the background handler, you must set the 'priority' to 'high' on Android and enable the content-available flag on iOS. For example, if using the Node.js firebase-admin package to send a message:

admin.messaging().sendToDevice(
  [], // device fcm tokens...
  {
    data: {
      owner: JSON.stringify(owner),
      user: JSON.stringify(user),
      picture: JSON.stringify(picture),
    },
  },
  {
    // Required for background/quit data-only messages on iOS
    contentAvailable: true,
    // Required for background/quit data-only messages on Android
    priority: 'high',
  },
);

For iOS specific 'data-only' messages, the message must include the appropriate APNs headers as well as the content-available flag in order to trigger the background handler. For example, if using the Node.js firebase-admin package to send a "data-only" message to an iOS device:

admin.messaging().send({
    data: {
      //some data
    },
    apns: {
      payload: {
        aps: {
          contentAvailable: true
        }
      },
      headers: {
        'apns-push-type': 'background',
        'apns-priority': '5',
        'apns-topic': '' // your app bundle identifier
      }
    },
    //must include token, topic, or condition
    //token: //device token
    //topic: //notification topic
    //condition: //notification condition
});

View the Sending Notification Requests to APNs documentation to learn more about APNs headers.

These options can be applied to all FCM messages. View the Server Integration documentation to learn more about other available SDKs.

Background Application State

The background notification handler is implemented slightly differently on both Android and iOS. On Android a headless JS task is created to listen to the messages which runs the handler separately from the React component.

On iOS however, when a message is received the device silently starts your application in a background state. Unlike Android, here the backgroundHandler is called along with the root of your application, which might and most probably will be a problem for you if you are relying on any kind of side effects (`useEffects` etc.). To get along with this problem, you can inject a isHeadless prop into your app and use this to conditionally render null instead of the root of your app.

// index.js
import { AppRegistry } from 'react-native';
import messaging from '@react-native-firebase/messaging';

messaging().setBackgroundMessageHandler(async remoteMessage => {
  console.log('Message handled in the background!', remoteMessage);
});

function HeadlessCheck({ isHeadless }) {
  if (isHeadless) {
    // App has been launched in the background by iOS, ignore
    return null;
  }

  return <App />;
}

function App() {
  // Your application
}

AppRegistry.registerComponent('app', () => HeadlessCheck);

You have to manually insert isHeadless prop into your app, you can do this by update your AppDelegate.m file:

// add this import statement at the top of your `AppDelegate.m` file
#import "RNFBMessagingModule.h"

// in "(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions" method
// Use `addCustomPropsToUserProps` to pass in props for initialization of your app
// Or pass in `nil` if you have none as per below example
// For `withLaunchOptions` please pass in `launchOptions` object
NSDictionary *appProperties = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions];

// Find the `RCTRootView` instance and update the `initialProperties` with your `appProperties` instance
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                             moduleName:@"nameOfYourApp"
                                             initialProperties:appProperties];

On Android, the root component does not get rendered when the app is in headless state hence the isHeadless prop will not exist.

The basic installation of FCM can be nice enough for just showing the notifications in the background, probably losing the ability of showing pop up notifications in the background. As soon you want any kind of control over you notifications it starts to get messier and if you are coming from earlier versions of FCM, you are in fact losing a significant amount of API support. FCM however recommends using third party libraries control your notifications and let FCM act as a messaging delivery service for you.

Notifee is a popular third party app which is recommended by invertase and is also a fork of earlier version.

Notifee :  Advanced Notifications

FCM provides support for displaying basic notifications to users with minimal integration required. However, if you require more advanced notifications, you need to integrate a 3rd party local notifications package, such as Notifee with your app.

Android Features

iOS Features

Installation

# Using npm
npm install --save @notifee/react-native

# Using Yarn
yarn add @notifee/react-native
# For iOS
cd ios/ && pod install --repo-update
npx react-native run-ios

# For Android
npx react-native run-android

Basic Usage

Displaying a Notification

function Screen() {
  async function onDisplayNotification() {
    // Create a channel
    const channelId = await notifee.createChannel({
      id: 'default',
      name: 'Default Channel',
    });

    // Display a notification
    await notifee.displayNotification({
      title: 'Notification Title',
      body: 'Main body content of the notification',
      android: {
        channelId,
      },
    });
  }

  return (
    <View>
      <Button title="Display Notification" onPress={() => onDisplayNotification()} />
    </View>
  );
}

When the button is pressed, we perform two tasks: creating a channel & displaying a notification.

Channels are an Android only concept used to categorize and allow users to control how notifications are handled on their devices. Channels are created with sensible default settings and are created or updated each time a call to createChannel is performed, so it is safe to keep calling this method.

Once the channel has been created, the displayNotification method is called passing in a title and body. The required channelId is also passed inside of the android property object to assign the notification to the channel.

On iOS platform, the call to createChannel resolves instantly & gracefully (iOS has no concept of a channel), then calls displayNotification.

Please go to https://notifee.app for detailed documentations on various customization with your notifications.

Alternatives to Notifee:

With Notifee requiring a paid license for release builds, we appreciate this model may not be viable for everyone. Other Open Source alternatives are also available if required:

Alternative to FCM:

OneSignal is a very popular notification service provider is a great alternative to FCM.

Thank you for reading! I hope that this article has given you the insight you need to try FCM and Notifee to implement better and more targeted notifications in your apps.

Hire our Development experts.