Push Notifications in React-Native with Firebase

Push Notifications in React-Native with Firebase
Using Firebase with React-Native Application

This blog will help you build a complete push notification engine for your react-native application using firebase. Just follow along and comment in the section below if you find any problems.

There are various open-source libraries that support sending push notifications to your react native applications. Few of the most popular libraries are react-native-fcm(1.7k stars), react-native-push-notifications(4.8k stars)react-native-firebase(7.4k stars). All of these libraries use FCM( Firebase Cloud Messaging ) as an underline integration. In this blog, we will be looking at the react-native-firebase library which I have used in multiple applications after finding it simple and very effective. This library supports all the firebase services like Crashlytics, Remote Config, and many others.

DISCLAIMER -> react-native-firebase v6 is not completely free and lot of functions are paid. If you want to support the community you can buy this verion of the product build by the same community for $240(one-time price).
  
NOTE -> This blog will use react-native-firebase version 5.
  
NOTE -> This library also work for ios with little bit different configuration and we are targeting android setup in this blog.

So let’s get started.

  1. Setup: There are two important steps:

(i) Firebase console: Set up your app on the firebase console and get the google-services.json file. This is a straightforward process and completely free of cost use this link.

(ii) Integrating react-native-firebase: Invertase has done a great job in maintaining their documentation and building a react-native-firebase library. To integrate the firebase with your application use this link provided by them.

Once you are done with the above two steps lets dive into a deeper understanding of multiple types of push notifications, actions, and integrating code for them.

2. Integrate complete Notifications code in your app:

It’s always best to use a separate file to handle notifications (HandleNotifications.js) instead of keeping it everywhere in your codebase. Mount this file in your index.android.js like below and bind it with a listener in your handle notification component.

import { AppRegistry, View, StyleSheet, AppState } from 'react-native';
import App from './App';
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from './configureStore';

import HandleNotifications from "./src/components/HandleNotifications/HandleNotifications";

const store = configureStore();

const AppContainer = () =>  
    <Provider store={store}>
        <View style={{ flex: 1, justifyContent: 'center', backgroundColor: 'white' }}>
            <App/>
            <HandleNotifications />
        </View>
    </Provider>;

AppRegistry.registerComponent('GreatApp', () => AppContainer); // GreatApp your app name

App.js

import React, { Component } from "react";
import firebase from 'react-native-firebase';

class HandleNotifications extends Component {
    constructor(props) {
        super(props);
        this.getToken = this.getToken.bind(this);
        this.requestPermission = this.requestPermission.bind(this);
        this.checkNotificationPermission = this.checkNotificationPermission.bind(this);
    }

    componentDidMount() {
        this.checkNotificationPermission();

        // setting channel for notification
        const channel = new firebase.notifications.Android.Channel(
            'channelId',
            'Channel Name',
            firebase.notifications.Android.Importance.Max
        ).setDescription('A natural description of the channel');
        firebase.notifications().android.createChannel(channel);

        // showing notification when app is in foreground.
        this.foregroundStateListener = firebase.notifications().onNotification((notification) => {
            firebase.notifications().displayNotification(notification).catch(err => console.error(err));
        });
        
        // app tapped/opened in killed state
        this.appKilledStateListener = firebase.notifications().getInitialNotification()
        .then((notificationOpen: NotificationOpen) => {
            if (notificationOpen) {
                // anything you want to do with notification object.....
            }
        });
        
        // app tapped/opened in foreground and background state
        this.notificationOpenedListener = firebase.notifications().onNotificationOpened((notificationOpen: NotificationOpen) => {
            // ...anything you want to do with notification object.....
        });
    }

    componentWillUnmount() {
        this.appKilledStateListener();
        this.notificationOpenedListener();
        this.foregroundStateListener();
    }

    // firebase token for the user
    async getToken(){
        firebase.messaging().getToken().then((fcmToken) => console.log(fcmToken));
    }
  
    // request permission if permission diabled or not given
    async requestPermission() {
        try {
            await firebase.messaging().requestPermission();
        } catch (error) {}
    }
  
    // if permission enabled get firebase token else request permission
    async checkNotificationPermission() {
        const enabled = await firebase.messaging().hasPermission();
        if (enabled) {
            this.getToken() // call function to get firebase token for personalized notifications.
        } else {
            this.requestPermission();
        }
    }
  
    render() {
        return null;
    }
}

export default HandleNotifications;

HandleNotifications.js

HandleNotifications.js file(above code) sets up everything for us including a channel( line 16 ). The Channel which should be different for different types of notifications and helps users to set actions for each type. The above code also sets the specific configuration that needs to handle showing a notification when the app is in the foreground( line 33 ). Three states of the app foreground( line 33 ), background( line 38 ), and killed( line 24 ) states are also handled separately to display a notification and to perform on-tap action.

3. Scheduling local notifications: Local notifications are best and easy to schedule as you don’t need to fire them using backend or Firebase console, just your application code. There are three scenarios to handle local notifications when the app in killed state, background, and foreground.

// Push Notifications constants
let baseNotification =  new firebase.notifications.Notification({
    sound: 'default',
    show_in_foreground: true,
})
    .setSound("default")
    .android.setChannelId('channelId') // id you have set in HandleNotifications.js
    .android.setColor('#ffffff') // you can set a color here
    .android.setPriority(firebase.notifications.Android.Priority.High)
    .android.setSmallIcon('ic_launcher')
    .android.setAutoCancel(true);

// notification actions
const takenAction = new firebase.notifications.Android.Action('medicine_taken', 'default', 'Taken');
const snoozAction = new firebase.notifications.Android.Action('snooz', 'default', '30 min Later');

// creating notification
const bigText = "Please take Medicine on time. Have you already taken it?";
baseNotification
    .setNotificationId('reminder')
    .setTitle('Reminder')
    .setBody(bigText)
    .setData({medicineId: '2'})
    .android.addAction(takenAction) // adding 
    .android.addAction(snoozAction); // snoozing

const schedule = {
    fireDate: new Date().getTime(),
};

// scheduling notifications
firebase.notifications().scheduleNotification(baseNotification, schedule);

Notifications.js

4. Sending remote notifications: This can be done in two ways, triggering from firebase console or triggering from your backend code.

(i) FireBase console: Go to firebase console -> Select your project -> Cloud messaging (left panel) -> Send your first message. Just set your parameters as per your notification requirement.

(ii) Backend notifications: There are multiple ways you can send these notifications like using curl, python, java, ruby, etc. These are generally helpful when you want user-specific notifications and not generic notifications. The basic curl command below will require FIREBASE KEY and FIREBASE TOKEN. To get FIREBASE KEY, Go to firebase console -> Select your project -> Settings (next to project overview in the left panel) -> Cloud Messaging -> Server key. Whereas FIREBASE TOKEN can be retrieved from the code in HandleNotifications.js (line 49, getToken function). After setting up these two, set the notification you want to send and you are done.

curl -X POST --header "Authorization: key=<FIREBASE KEY>" \
    --Header "Content-Type: application/json" \
    https://fcm.googleapis.com/fcm/send \
    -d "{\"to\":\"<FIREBASE TOKEN>\",\"notification\":{\"body\":\"ENTER YOUR NOTIFICATION MESSAGE HERE\"}"

5. Actions with notifications: There are two types of actions that are generally performed with notifications, i) silent actions and ii) on tap actions.

(i) Silent Actions: If you don’t want the user to go to the application and perform some action from the notification itself, actions like snooze, medicine is taken, remind me later, reply from notifications are such actions, also called Headless Actions.

Add the below code in your android/app/src/main/AndroidManifest.xml

<application ...>
  <receiver android:name="io.invertase.firebase.notifications.RNFirebaseBackgroundNotificationActionReceiver" android:exported="true">
    <intent-filter>
      <action android:name="io.invertase.firebase.notifications.BackgroundAction"/>
    </intent-filter>
  </receiver>
  <service android:name="io.invertase.firebase.notifications.RNFirebaseBackgroundNotificationActionsService"/>
</application>

Register Headless task in your index.android.js

// path of your HeadlessNotifications file.
import HeadlessNotifications from "./src/HeadlessNotifications";
// Headless task
AppRegistry.registerHeadlessTask('RNFirebaseBackgroundNotificationAction', () => HeadlessNotifications);

Put the below code in HeadlessNotifications.js

import firebase from 'react-native-firebase';
import type { NotificationOpen } from 'react-native-firebase';

export default async (notificationOpen: NotificationOpen) => {
    if (notificationOpen.action === 'snooze') {
        // handle notification.
    }

    return Promise.resolve();
}

HeadlessNotifications.js

(ii) On tap Actions: If the app developer wants the user to go inside the notification and perform some action. For such actions generally, the user is redirected to a specific screen. Use your existing app router for that purpose. It's fairly simple and provided by the library community itself.

That looked like a lot of work to do but I am sure once you see that your users are able to get the notifications it’s going to be worth doing. Also, I have given all the possible requirements of your application notification engine, you can choose which one you want to drop from your codebase.

Now you will be able to better communicate with your app users and improve the user engagement of your application. HAVE FUN!!!