How to set up Firebase Notification in React-native App (Android only)

Yang Nana
10 min readJul 6, 2018

--

What I edited, applied, I make it bold for you to see my code.

Settings

For now, I don’t have the Paid Apple Developer Account so I only test on Android device. It’s my first time so I haven’t tried with Push Notification yet. I only tried with pure firebase notification.

There are the steps on rnfirebase.io if you are interested. I only combined them together, and I added some when I got stuck.

First thing first, I created a new project.

react-native init fcm
cd fcm
yarn
yarn add react-native-firebase
react-native link react-native-firebase

This is my package.json

{
"name": "fcm",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"react": "16.4.1",
"react-native": "0.56.0",
"react-native-firebase": "^4.2.0"
},
"devDependencies": {
"babel-jest": "23.2.0",
"babel-preset-react-native": "^5",
"jest": "23.3.0",
"react-test-renderer": "16.4.1"
},
"jest": {
"preset": "react-native"
}
}

Go to Firebase console (https://firebase.google.com/console), create a project there.

My package is ‘com.fcm’. How to know? There is android/app/src/main/java/com/fcm/MainApplication.java. In that file you can see something like this

package com.fcm;

Or you go to app/build.gradle. You can see something like this

applicationId "com.fcm"

It is the package name we need when we create the Android app in Firebase.

Create an Android app, download google-services.json file.

Copy that json file to android/app

Go to android/build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
repositories {
google()
jcenter()
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
classpath 'com.google.gms:google-services:3.2.1'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

allprojects {
repositories {
mavenLocal()
google()
jcenter()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
}

ext {
buildToolsVersion = "26.0.3"
minSdkVersion = 16
compileSdkVersion = 26
targetSdkVersion = 26
supportLibVersion = "26.1.0"
}

Go to android/app/build.gradle

project.ext.react = [
entryFile: "index.js"
]

apply from: "../../node_modules/react-native/react.gradle"

/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false

/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false

android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion


defaultConfig {
applicationId "com.fcm"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion

versionCode 1
versionName "1.0"
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86"
}
}
buildTypes {
release {
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
def versionCodes = ["armeabi-v7a":1, "x86":2]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
}
dependencies {
implementation project(':react-native-firebase')
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "com.google.android.gms:play-services-base:15.0.0"
implementation "com.google.firebase:firebase-core:15.0.2"
implementation "com.google.firebase:firebase-messaging:15.0.2"
implementation 'me.leolin:ShortcutBadger:1.1.21@aar'

}

// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
apply plugin: 'com.google.gms.google-services'

Go to android/gradle/wrapper/gradle-wrapper.properties

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

Go to android/app/src/main/java/com/fcm/MainApplication.java

package com.fcm;import android.app.Application;import com.facebook.react.ReactApplication;
import io.invertase.firebase.RNFirebasePackage;
import io.invertase.firebase.messaging.RNFirebaseMessagingPackage;
import io.invertase.firebase.notifications.RNFirebaseNotificationsPackage;

import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import java.util.Arrays;
import java.util.List;
public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNFirebasePackage(),
new RNFirebaseMessagingPackage(),
new RNFirebaseNotificationsPackage()

);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}

Go to android/app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.fcm">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />


<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:allowBackup="false"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service android:name="io.invertase.firebase.messaging.RNFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name="io.invertase.firebase.messaging.RNFirebaseInstanceIdService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>
<service android:name="io.invertase.firebase.messaging.RNFirebaseBackgroundMessagingService" />

</application>

</manifest>

Try to run it to see it there is any problems

react-native run-android

If there is, it’s better you try Google it ~^^. I haven’t got any problem with this settings.

Coding

Go to your App.js file

/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow
*/
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import firebase from 'react-native-firebase';
import type { Notification, NotificationOpen } from 'react-native-firebase';
const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
android:
'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});
export default class App extends Component {
async componentDidMount() {
const notificationOpen: NotificationOpen = await firebase.notifications().getInitialNotification();
if (notificationOpen) {
const action = notificationOpen.action;
const notification: Notification = notificationOpen.notification;
var seen = [];
alert(JSON.stringify(notification.data, function(key, val) {
if (val != null && typeof val == "object") {
if (seen.indexOf(val) >= 0) {
return;
}
seen.push(val);
}
return val;
}));
}
const channel = new firebase.notifications.Android.Channel('test-channel', 'Test Channel', firebase.notifications.Android.Importance.Max)
.setDescription('My apps test channel');
// Create the channel
firebase.notifications().android.createChannel(channel);
this.notificationDisplayedListener = firebase.notifications().onNotificationDisplayed((notification: Notification) => {
// Process your notification as required
// ANDROID: Remote notifications do not contain the channel ID. You will have to specify this manually if you'd like to re-display the notification.
});
this.notificationListener = firebase.notifications().onNotification((notification: Notification) => {
// Process your notification as required
notification
.android.setChannelId('test-channel')
.android.setSmallIcon('ic_launcher');
firebase.notifications()
.displayNotification(notification);

});
this.notificationOpenedListener = firebase.notifications().onNotificationOpened((notificationOpen: NotificationOpen) => {
// Get the action triggered by the notification being opened
const action = notificationOpen.action;
// Get information about the notification that was opened
const notification: Notification = notificationOpen.notification;
var seen = [];
alert(JSON.stringify(notification.data, function(key, val) {
if (val != null && typeof val == "object") {
if (seen.indexOf(val) >= 0) {
return;
}
seen.push(val);
}
return val;
}));
firebase.notifications().removeDeliveredNotification(notification.notificationId);

});
}
componentWillUnmount() {
this.notificationDisplayedListener();
this.notificationListener();
this.notificationOpenedListener();
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<Text style={styles.instructions}>To get started, edit App.js</Text>
<Text style={styles.instructions}>{instructions}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

How to test? There are two ways

I will present you the Notification Console, cause it’s the most easiest one. The rest I am making it.

Go to this link (https://console.firebase.google.com/project/_/notification), choose your project and start to send the message.

The message will show in your Android app as a notification.

Remind:

Be sure to apply the notification channel name, or else background and killed version not display the headup

That’s done~

(I made the source at this site: https://github.com/yangnana11/react-native-fcm-demo)

Problems with older Android version (Foreground)

If we test with Firebase Console, there is some info missing which make it fails in Android old version.

For older Android phone (Android version 7.1 and less) the heads up not show up with this configure.

Method 1

Go to node_modules\react-native-firebase\android\src\main\java\io\invertase\firebase\notifications\DisplayNotificationTask.java

Find then make change these lines. It will show heads up for foreground stage in Android old version.

import android.media.RingtoneManager;
...
if (notification.containsKey("sound")) {
Uri sound = RNFirebaseNotificationManager.getSound(context, notification.getString("sound"));
nb = nb.setSound(sound);
} else {
nb = nb.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));
}
...
if (android.containsKey("priority")) {
Double priority = android.getDouble("priority");
nb = nb.setPriority(priority.intValue());
} else {
nb = nb.setPriority(NotificationCompat.PRIORITY_MAX);
}

Added: Method 2

You can just modify the above code

this.notificationListener = firebase.notifications().onNotification((notification: Notification) => {
// Process your notification as required
notification
.android.setChannelId('test-channel')
.android.setSmallIcon('ic_launcher')
.android.setPriority(firebase.notifications.Android.Priority.Max)
.setSound('default')
firebase.notifications()
.displayNotification(notification)
})

But it only work on foreground.

Added: Background settings

For background, be sure to have notification sound and urgent priority for android 7.1 and less, and channel info for android 8.0 and more. (reference: https://developer.android.com/guide/topics/ui/notifiers/notifications)

Level 1: Only care about new version (Android 8.0 and more)

{
"notification": {
"title": "title",
"text": "your text",
"body": "body",
"sound": "default",
"android_channel_id": "test-channel",
"high_priority": "high",
"show_in_foreground": true
},
"priority": "high",
"to": "eu...otVKCbii1-l"
}

Level 2: Care about new version and old version and don’t care about the notification information

We don’t care about the information it gives, we just care

  • Receive the head up
  • When we click the head up, it opens the app.

The first step, from server, we won’t have notification, we only have data. So it can go to onMessageReceived()

This is the example of json

{
"data": {
"title": "title",
"body": "body",
"android_channel_id": "test-channel"
},
"priority": "high",
"to": "eu...otVKCbii1-l"
}

The second step, I copied the icon to react-native-firebase folder, like this

The third step, create a new file name NotificationService.java

And this is its content

package com.nextico_app;

import android.content.Intent;
import android.content.Context;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.app.PendingIntent;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationChannel;
import android.graphics.Color;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import io.invertase.firebase.R;

import com.facebook.react.HeadlessJsTaskService;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

import io.invertase.firebase.Utils;

import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;

import org.json.JSONObject;
import org.json.JSONException;

import com.nextico_app.MainActivity;

public class NotificationService extends FirebaseMessagingService {

private static final String TAG = "NotificationService";
public static final String REMOTE_NOTIFICATION_EVENT = "notifications-remote-notification";

@Override
public void onMessageReceived(RemoteMessage message) {
Log.d(TAG, "onMessageReceived event received");
try {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);

Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder;
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

notificationBuilder = new NotificationCompat.Builder(this, message.getData().get("android_channel_id"))
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(message.getData().get("title"))
.setContentText(message.getData().get("body"))
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setContentIntent(pendingIntent)
.setContentText(message.getData().get("body"));
} else {
notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(message.getData().get("title"))
.setContentText(message.getData().get("body"))
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setContentIntent(pendingIntent)
.setContentText(message.getData().get("body"));
}

notificationManager.notify(0, notificationBuilder.build());
} catch (Exception e) {
Log.d(TAG, "Error ", e);
}
}
}

The last step, going to AndroidManifest.xml and edit the file

<service android:name=".NotificationService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

Level 3: Care about new and old version, and care about the information

Unfortunately, I can not find any simple solution with my Java skill. So I made a workaround in firebase library. I am sorry if this is not what you hope for… I hope you can find another better solution for yourself.

Step 1, be sure the server only have data tag

{
"data": {
"title": "title",
"body": "body",
"android_channel_id": "my-channel"
},
"priority": "high",
"to": "/topics/my-topic-news"
}

Step 2, go to RNFirebaseMessagingService.java and edit the code

@Override
public void onMessageReceived(RemoteMessage message) {
Log.d(TAG, "onMessageReceived event received");

Intent notificationEvent = new Intent(REMOTE_NOTIFICATION_EVENT);
notificationEvent.putExtra("notification", message);

// Broadcast it to the (foreground) RN Application
LocalBroadcastManager.getInstance(this).sendBroadcast(notificationEvent);
}

Step 3, go to RNFirebaseNotifications.java and edit the code

private WritableMap parseRemoteMessage(RemoteMessage message) {
//RemoteMessage.Notification notification = message.getData();

WritableMap notificationMap = Arguments.createMap();
WritableMap dataMap = Arguments.createMap();

// Cross platform notification properties
String body = message.getData().get("body");
//String body = getNotificationBody(notification);
if (body != null) {
notificationMap.putString("body", body);
}
if (message.getData() != null) {
for (Map.Entry<String, String> e : message.getData().entrySet()) {
dataMap.putString(e.getKey(), e.getValue());
}
}
notificationMap.putMap("data", dataMap);
if (message.getMessageId() != null) {
notificationMap.putString("notificationId", message.getMessageId());
}
/*if (notification.getSound() != null) {
notificationMap.putString("sound", notification.getSound());
}
String title = getNotificationTitle(notification);
String title = notification.get("title");*/
String title = message.getData().get("title");
if (title != null) {
notificationMap.putString("title", title);
}

// Android specific notification properties
/*WritableMap androidMap = Arguments.createMap();
if (notification.getClickAction() != null) {
androidMap.putString("clickAction", notification.getClickAction());
}
if (notification.getColor() != null) {
androidMap.putString("color", notification.getColor());
}
if (notification.getIcon() != null) {
WritableMap iconMap = Arguments.createMap();
iconMap.putString("icon", notification.getIcon());
androidMap.putMap("smallIcon", iconMap);
}
if (notification.getTag() != null) {
androidMap.putString("group", notification.getTag());
androidMap.putString("tag", notification.getTag());
}
notificationMap.putMap("android", androidMap);*/

return notificationMap;
}

Step 4, go to Display NotificationTask.java and edit the code

import android.media.RingtoneManager;
...
if (notification.containsKey("sound")) {
Uri sound = RNFirebaseNotificationManager.getSound(context, notification.getString("sound"));
nb = nb.setSound(sound);
} else {
nb = nb.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));
}
...
nb = nb.setPriority(NotificationCompat.PRIORITY_MAX);

Step 5, modify the App.js file to check the result

this.notificationOpenedListener = firebase.notifications().onNotificationOpened((notificationOpen: NotificationOpen) => {
// Get the action triggered by the notification being opened
console.log(notificationOpen);
const action = notificationOpen.action
// Get information about the notification that was opened
const notification: Notification = notificationOpen.notification
var seen = []
alert(JSON.stringify(notificationOpen, function (key, val) {
if (val != null && typeof val === 'object') {
if (seen.indexOf(val) >= 0) {
return
}
seen.push(val)
}
return val
}))

This is the result

I think this way will have a lot workaround, but this is a lead way…

I hope this help somehow

Added: Subscribe Topic for new Firebase version

There is a problem when I start making the notification again. They upgrade the versions and subscribeToTopic won’t work anymore. If you have the same problem as me, here are the tips

In your package.json you change the react-native-firebase version

"react-native-firebase": "^4.3.0",

In app/build.gradle you will also change the versions

dependencies {
implementation project(':react-native-firebase')
implementation "com.google.firebase:firebase-core:16.0.1"
implementation "com.google.android.gms:play-services-base:15.0.1"
implementation "com.google.firebase:firebase-messaging:17.1.0"
}

That is all. Have fun with topic~

They are all tested with debug mode. For any problems with release tested… I have no idea. Sorry~

--

--