Skip to main content

React Native

Learn how to set up Push notifications for React Native using Firebase Cloud Messaging or FCM.

I want to checkout the sample app

React Native Push notifications sample app

View on Github

Firebase Project Setup

Visit Firebase and login/signup using your Gmail ID.

Step 1: Create a new Firebase Project

Head over to the Firebase Console to create a new project.

Image

This is a simple 3 step process where:

  1. You give a name to your project
  2. Add Google Analytics to your project (Optional)
  3. Configure Google Analytics account (Optional)

Click on Create and you are ready to go.

Step 2: Add Firebase to your App

React native setup will require 2 files for Android and iOS:

  1. For Android, you need to download the google-services.json file. You can refer to the Android Firebase Project Setup - Step 2 and resume here once done.
  2. For iOS, you need to download the GoogleService-Info.plist file. You can refer to the iOS Firebase Project Setup - Step 2 and resume here once done.

Step 3: Download the service account file

Image

Extension settings

Step 1: Enable the extension

  1. Login to CometChat and select your app.
  2. Go to the Extensions section and Enable the Push Notifications extension.
  3. Open up the settings and save the following settings.
Image

Step 2: Save your settings

On the Settings page you need to enter the following:

  1. Set extension version
  • If you are setting it for the first time, Select V2 to start using the enhanced version of the Push Notification extension. The enhanced version uses Token-based approach for sending Push Notifications and is simple to implement.
  • If you already have an app using V1 and want to migrate your app to use V2, then Select V1 & V2 option. This ensures that the users viewing the older version of your app also receive Push Notifications.
  • Eventually, when all your users are on the latest version of your app, you can change this option to V2, thus turning off V1 (Topic-based) Push Notifications completely.
  1. Select the platforms that you want to support
  • Select from Web, Android, Ionic, React Native, Flutter & iOS.
  1. Notification payload settings
  • You can control if the notification key should be in the Payload or not. Learn more about the FCM Messages here.
  1. Push payload message options
Image
  • The maximum payload size supported by FCM and APNs for push notifications is approximately 4 KB. Due to the inclusion of CometChat's message object, the payload size may exceed this limit, potentially leading to non-delivery of push notifications for certain messages. The options provided allow you to remove the sender's metadata, receiver's metadata, and message metadata that may not be necessary for displaying push notifications on end-user devices.

  • The message metadata includes the outputs of the Thumbnail Generation, Image Moderation, and Smart Replies extensions. You may want to retain this metadata if you need to customize the notification displayed to the end user based on these outputs.

  1. Notification Triggers
Image
  • Select the triggers for sending Push Notifications. These triggers can be classified into 3 main categories:
    1. Message Notifications
    2. Call Notifications
    3. Group Notifications
  • These are pretty self-explanatory and you can toggle them as per your requirement.

App Setup

Step 1: Initial plugin setup

  1. For React Native, there are numerous plugins available via NPM which can be used to set up push notifications for your apps. react-native-firebase and react-native-notifications are just the two out of many available.
  2. To setup Push Notification, you need to follow the steps mentioned in the Plugin's Documentation.

At this point, you will have:

  1. Two separate apps created on the Firebase console. (For Android and iOS).
  2. Plugin setup completed as per the respective documentation and our reference.

Step 2: Register FCM Token

  1. This step assumes that you already have a React Native app setup with CometChat installed. Make sure that the CometChat object is initialized and user has been logged in.
  2. On the success callback of user login, you can fetch the FCM Token and register it with the extension as shown below:
// Pseudo-code with async-await syntax

const APP_ID = 'APP_ID';
const REGION = 'REGION';
const AUTH_KEY = 'AUTH_KEY';

const UID = 'UID';
const APP_SETTINGS = new CometChat.AppSettingsBuilder()
.subscribePresenceForAllUsers()
.setRegion(REGION)
.build();

try {
// First initialize the app
await CometChat.init(APP_ID, APP_SETTINGS);

// Login the user
await CometChat.login(UID, AUTH_KEY);

// Get the FCM device token
// You should have imported the following in the file:
// import messaging from '@react-native-firebase/messaging';
const FCM_TOKEN = await messaging().getToken();

// Register the token with Enhanced Push Notifications extension
await CometChat.registerTokenForPushNotification(FCM_TOKEN);
} catch (error) {
// Handle errors gracefully
}
  1. Registration also needs to happen in case of token refresh as shown below:
// Pseudo-code

// You should have imported the following in the file:
// import messaging from '@react-native-firebase/messaging';
try {
// Listen to whether the token changes
return messaging().onTokenRefresh(FCM_TOKEN => {
await CometChat.registerTokenForPushNotification(FCM_TOKEN);
});
// ...
} catch(error) {
// Handle errors gracefully
}

For React Native Firebase reference, visit the link below:

Step 3: Receive Notifications

// Pseudo-code
import messaging from '@react-native-firebase/messaging';
import { Alert } from 'react-native';

// Implementation can be done in a life-cycle method or hook
const unsubscribe = messaging().onMessage(async (remoteMessage) => {
Alert.alert('A new FCM message arrived!', JSON.stringify(remoteMessage));
});
info

We send Data Notifications and you need to handle displaying notifications at your end. For eg: Using Notifee

Step 4: Stop receiving Notifications

  1. Simply logout the CometChat user and you will stop receiving notifications.
  2. As a good practice, you can also delete the FCM Token by calling deleteToken on the messaging object.
// Pseudo-code using async-await syntax

logout = async () => {
// User logs out of the app
await CometChat.logout();

// You should have imported the following in the file:
// import messaging from '@react-native-firebase/messaging';
// This is a good practice.
await messaging().deleteToken();
};

Advanced

Handle Custom Messages

To receive notification of CustomMessage, you need to set metadata while sending the CustomMessage.

var receiverID = 'UID';
var customData = {
latitude: '50.6192171633316',
longitude: '-72.68182268750002',
};

var customType = 'location';
var receiverType = CometChat.RECEIVER_TYPE.USER;
var metadata = {
pushNotification: 'Your Notification Message',
};

var customMessage = new CometChat.CustomMessage(
receiverID,
receiverType,
customType,
customData
);

customMessage.setMetadata(metadata);

CometChat.sendCustomMessage(customMessage).then(
(message) => {
// Message sent successfully.
console.log('custom message sent successfully', message);
},
(error) => {
console.log('custom message sending failed with error', error);
// Handle exception.
}
);

Converting push notification payload to message object

CometChat SDK provides a method CometChat.CometChatHelper.processMessage() to convert the message JSON to the corresponding object of TextMessage, MediaMessage,CustomMessage, Action or Call.

var processedMessage = CometChat.CometChatHelper.processMessage(JSON_MESSAGE);
info

Type of Attachment can be of the following the type
1.CometChatConstants.MESSAGE_TYPE_IMAGE
2.CometChatConstants.MESSAGE_TYPE_VIDEO
3.CometChatConstants.MESSAGE_TYPE_AUDIO
4.CometChatConstants.MESSAGE_TYPE_FILE

Push Notification: Payload Sample for Text Message and Attachment/Media Message

{
"alert": "Nancy Grace: Text Message",
"sound": "default",
"title": "CometChat",
"message": {
"receiver": "cometchat-uid-4",
"data": {
"entities": {
"receiver": {
"entityType": "user",
"entity": {
"uid": "cometchat-uid-4",
"role": "default",
"name": "Susan Marie",
"avatar": "https://assets.cometchat.io/sampleapp/v2/users/cometchat-uid-4.webp",
"status": "offline"
}
},
"sender": {
"entityType": "user",
"entity": {
"uid": "cometchat-uid-3",
"role": "default",
"name": "Nancy Grace",
"avatar": "https://assets.cometchat.io/sampleapp/v2/users/cometchat-uid-3.webp",
"status": "offline"
}
}
},
"text": "Text Message"
},
"sender": "cometchat-uid-3",
"receiverType": "user",
"id": "142",
"sentAt": 1555668711,
"category": "message",
"type": "text"
}
}

Integrating ConnectionService and CallKit Using CometChat Push Notification

Image
Image
Note
  • Currently we can only handle default calling notification
  • Whenever the user answers the call we use RNCallKeep.backToForeground(); method to bring the app in to foreground but in some devices you might need to add few more permissions for this to work For example, In MIUI 11 you need to permission for Display pop-up windows while running in the background
  • When the IOS app is in lock state we are not able to open the app so the call start on callkeep it self and you can hear the audio but if you want a video call then the user has to unlock the phone click on the app icon on call screen.
  • If you want to use the callkit and connection service in foreground then you might consider turning the callNotifications settings in UI kit settings. For more information in UI kit settings check the documentation.

Setup push notification

  • Android

Kindly follow the instruction for setting Firebase Cloud Messaging explained here

  • IOS

For IOS we use Apple Push Notification service or APNs to send push notification and VOIP notification. To configure this we need to follow some additional steps

Step 1: Create a Certificate Signing Request

To obtain a signing certificate required to sign apps for installation on iOS devices, you should first create a certificate signing request (CSR) file through Keychain Access on your Mac.

  1. Open the Keychain Access from the utility folder, go to Keychain Access > Certificate Assistant > Request a Certificate From a Certificate Authority, and then click.
Image
  1. The Certificate Information dialog box appears. Enter the email address that you use in your Apple Developer account, and enter a common name for your private key. Don't enter CA email address, choose Saved to disk, and then click the Continue button. <img align="center" src="./images/step1.2.png"> <br></br>
  2. Specify the name of your CSR to save and choose the location to save the file on your local disk. Then your CSR file is created, which contains a public/private key pair.

Step 2: Create an SSL certificate

  1. Sign in to your account at the Apple Developer Member Center.
  2. Go to Certificates, Identifiers & Profiles.
Image
  1. Create new Certificate by clicking on the + icon.
Image
  1. Under Services, select - Apple Push Notification services SSL (Sandbox & Production)
Image
  1. Select your App ID from the dropdown.
Image
  1. Upload CSR file., upload the CSR file you created through the Choose File button. To complete the process, choose Continue. When the certificate is ready, choose Download to save it to your Mac.
Image
Image

Step 3: Export and update .p8 certificate

  1. To generate a .p8 key file, go to Apple developer account page, then select Certificates, IDs & Profiles.
  2. Select Keys and click on the "+" button to add a new key.
  3. In the new key page, type in your key name and check the Apple Push Notification service (APNs) box, then click "Continue" and click "Register".
  4. Then proceed to download the key file by clicking Download.
  5. Make note of the Key ID, Team ID and your Bundle ID for saving in the Extension's settings.

If you wish to use the .p12 certificate instead, do the following:

  1. Type a name for the .p12 file and save it to your Mac.
  2. Browse to the location where you saved your key, select it, and click Open. Add the key ID for the key (available in Certificates, Identifiers & Profiles in the Apple Developer Member Center) and export it.
  3. DO NOT provide an export password when prompted.
  4. The .p12 file will be required in the next step for uploading in the CometChat Dashboard.

Extension settings

Step 1: Enable the extension

  1. Login to CometChat and select your app.
  2. Go to the Extensions section and Enable the Push Notifications extension.
  3. Open the settings for this extension and save the following.
Image

Step 2: Save your settings

On the Settings page you need to enter the following:

Image
  1. Set extension version

    The extension version has to be set to 'V2' or 'V1 & V2' in order to use APNs as the provider.

  2. Select Platforms

    You can select the platforms on which you wish to receive Push Notifications.

  3. Firebase Cloud Messaging Settings

    This includes the FCM Server key that you can fetch from the Firebase Dashboard.

  4. APNs Settings

    You can turn off the Production mode when you create a development build of your application. Upload the .p12 certificate exported in the previous step.

  5. Push Notifications Title

    This is usually the name of your app.

  6. Notification Triggers

    Select the triggers for sending Push Notifications. These triggers can be classified into 3 main categories:

    1. Message Notifications
    2. Call Notifications
    3. Group Notifications

    These are pretty self-explanatory and you can toggle them as per your requirement.

Installation

We need to add two packages for this

  • React-native-CallKeep

This package also require some additional installation steps. Follow this link to install react-native-callkeep

npm install react-native-callkeep
//or
yarn add react-native-callkeep
  • React Native VoIP Push Notification

This package also require some additional installation steps. Follow this link to install react-native-voip-push-notification.

npm install react-native-voip-push-notification
# --- if using pod
cd ios/ && pod install

App Setup

First you need to Setup CallKeep at the start of the app in Index.js

const options = {
ios: {
appName: 'My app name',
},
android: {
alertTitle: 'Permissions required',
alertDescription: 'This application needs to access your phone accounts',
cancelButton: 'Cancel',
okButton: 'ok',
imageName: 'phone_account_icon',

foregroundService: {
channelId: 'com.company.my',
channelName: 'Foreground service for my app',
notificationTitle: 'My app is running on background',
notificationIcon: 'Path to the resource icon of the notification',
},
},
};
RNCallKeep.setup(options);
RNCallKeep.setAvailable(true);
let callKeep = new CallKeepHelper();

In order to handle connectionService and CallKit we have made a helper call.

import { CometChat } from '@cometchat/chat-sdk-react-native';
import { Platform } from 'react-native';
import uuid from 'react-native-uuid';
import RNCallKeep, { AnswerCallPayload } from 'react-native-callkeep';
import { navigate } from '../StackNavigator';
import messaging from '@react-native-firebase/messaging';
import VoipPushNotification from 'react-native-voip-push-notification';
import invokeApp from 'react-native-invoke-app';
import KeepAwake from 'react-native-keep-awake';
import { AppState } from 'react-native';
import _BackgroundTimer from 'react-native-background-timer';
export default class CallKeepHelper {
constructor(msg) {
if (msg) {
CallKeepHelper.msg = msg;
}
this.setupEventListeners();
this.registerToken();
this.checkLoggedInUser();
this.addLoginListener();
CallKeepHelper.callEndedBySelf = false;
}
static FCMToken = null;
static voipToken = null;
static msg = null;
static callEndedBySelf = null;
static callerId = '';
static callerId1 = '';
static isLoggedIn = false;
checkLoggedInUser = async () => {
try {
let user = await CometChat.getLoggedinUser();
if (user) {
if (user) {
CallKeepHelper.isLoggedIn = true;
}
}
} catch (error) {
console.log('error checkLoggedInUser', error);
}
};

addLoginListener = () => {
var listenerID = 'UNIQUE_LISTENER_ID';
CometChat.addLoginListener(
listenerID,
new CometChat.LoginListener({
loginSuccess: (e) => {
CallKeepHelper.isLoggedIn = true;
this.registerTokenToCometChat();
},
})
);
};

registerTokenToCometChat = async () => {
if (!CallKeepHelper.isLoggedIn) {
return false;
}

try {
if (Platform.OS == 'android') {
if (CallKeepHelper.FCMToken) {
let response = await CometChat.registerTokenForPushNotification(
CallKeepHelper.FCMToken
);
}
} else {
if (CallKeepHelper.FCMToken) {
let response = await CometChat.registerTokenForPushNotification(
CallKeepHelper.FCMToken,
{ voip: false }
);
}
if (CallKeepHelper.voipToken) {
let response = await CometChat.registerTokenForPushNotification(
CallKeepHelper.voipToken,
{ voip: true }
);
}
}
} catch (error) {}
};

registerToken = async () => {
try {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
if (Platform.OS == 'android') {
let FCM = await messaging().getToken();

CallKeepHelper.FCMToken = FCM;
this.registerTokenToCometChat();
} else {
VoipPushNotification.registerVoipToken();
let FCM = await messaging().getAPNSToken();
CallKeepHelper.FCMToken = FCM;
this.registerTokenToCometChat();
}
}
} catch (error) {}
};

endCall = ({ callUUID }) => {
if (CallKeepHelper.callerId) RNCallKeep.endCall(CallKeepHelper.callerId);
_BackgroundTimer.start();
setTimeout(() => {
this.rejectCall();
}, 3000);
};

rejectCall = async () => {
if (
!CallKeepHelper.callEndedBySelf &&
CallKeepHelper.msg &&
CallKeepHelper.msg.call?.category !== 'custom'
) {
var sessionID = CallKeepHelper.msg.sessionId;
var status = CometChat.CALL_STATUS.REJECTED;
let call = await CometChat.rejectCall(sessionID, status);
_BackgroundTimer.stop();
} else {
_BackgroundTimer.stop();
}
};

static displayCallAndroid = () => {
this.IsRinging = true;
CallKeepHelper.callerId = CallKeepHelper.msg.conversationId;
RNCallKeep.displayIncomingCall(
CallKeepHelper.msg.conversationId,
CallKeepHelper.msg.sender.name,
CallKeepHelper.msg.sender.name,
'generic'
);
setTimeout(() => {
if (this.IsRinging) {
this.IsRinging = false;
RNCallKeep.reportEndCallWithUUID(CallKeepHelper.callerId, 6);
}
}, 15000);
};

// NOTE: YOU MIGHT HAVE TO MAKE SOME CHANGES OVER HERE AS YOU AS YOUR IMPLEMENTATION OF REACT-NATIVE-UI-KIT MIGHT BE DIFFERENT. YOU JUST NEED TO CALL THE ACCEPT CALL METHOD AND NAVIGATE TO CALL SCREEN.
answerCall = ({ callUUID }) => {
this.IsRinging = false;
CallKeepHelper.callEndedBySelf = true;
setTimeout(
() =>
navigate({
index: 0,
routes: [
{ name: 'Conversation', params: { call: CallKeepHelper.msg } },
],
}),
2000
);
// RNCallKeep.endAllCalls();
RNCallKeep.backToForeground();
if (Platform.OS == 'ios') {
if (AppState.currentState == 'active') {
RNCallKeep.endAllCalls();
_BackgroundTimer.stop();
} else {
this.addAppStateListener();
}
} else {
RNCallKeep.endAllCalls();
_BackgroundTimer.stop();
}
};

addAppStateListener = () => {
AppState.addEventListener('change', (newState) => {
if (newState == 'active') {
RNCallKeep.endAllCalls();
_BackgroundTimer.stop();
}
});
};

didDisplayIncomingCall = (DidDisplayIncomingCallArgs) => {
if (DidDisplayIncomingCallArgs.callUUID) {
if (Platform.OS == 'ios') {
CallKeepHelper.callerId = DidDisplayIncomingCallArgs.callUUID;
}
}
if (DidDisplayIncomingCallArgs.error) {
console.log({
message: `Callkeep didDisplayIncomingCall error: ${DidDisplayIncomingCallArgs.error}`,
});
}

this.IsRinging = true;

setTimeout(() => {
if (this.IsRinging) {
this.IsRinging = false;
// 6 = MissedCall
// https://github.com/react-native-webrtc/react-native-callkeep#constants
RNCallKeep.reportEndCallWithUUID(
DidDisplayIncomingCallArgs.callUUID,
6
);
}
}, 15000);
};

setupEventListeners() {
if (Platform.OS == 'ios') {
CometChat.addCallListener(
'this.callListenerId',
new CometChat.CallListener({
onIncomingCallCancelled: (call) => {
RNCallKeep.endAllCalls();
},
})
);

RNCallKeep.addEventListener('didLoadWithEvents', (event) => {
for (let i = 0; i < event.length; i++) {
if (event[i]?.name == 'RNCallKeepDidDisplayIncomingCall') {
CallKeepHelper.callerId = event[i]?.data?.callUUID;
}
}
});

VoipPushNotification.addEventListener('register', async (token) => {
CallKeepHelper.voipToken = token;
this.registerTokenToCometChat();
});
VoipPushNotification.addEventListener('notification', (notification) => {
let msg = CometChat.CometChatHelper.processMessage(
notification.message
);

CallKeepHelper.msg = msg;
});

VoipPushNotification.addEventListener(
'didLoadWithEvents',
async (events) => {
if (!events || !Array.isArray(events) || events.length < 1) {
return;
}
for (let voipPushEvent of events) {
let { name, data } = voipPushEvent;
if (
name ===
VoipPushNotification.RNVoipPushRemoteNotificationsRegisteredEvent
) {
CallKeepHelper.voipToken = data;
} else if (
name ===
VoipPushNotification.RNVoipPushRemoteNotificationReceivedEvent
) {
let msg = CometChat.CometChatHelper.processMessage(data.message);

CallKeepHelper.msg = msg;
}
}
}
);
}

RNCallKeep.addEventListener('endCall', this.endCall);

RNCallKeep.addEventListener('answerCall', this.answerCall);
}

removeEventListeners() {
RNCallKeep.removeEventListener('endCall');
RNCallKeep.removeEventListener('didDisplayIncomingCall');
RNCallKeep.removeEventListener('didLoadWithEvents');
VoipPushNotification.removeEventListener('didLoadWithEvents');
VoipPushNotification.removeEventListener('register');
VoipPushNotification.removeEventListener('notification');
}
}

Android

In android we are going to use Firebase push notification to display Call notification So basically when ever we receive a push notification for call we display call notification.

we need to add a listener to listen to notification when the app is background or foreground state.

messaging().setBackgroundMessageHandler(async (remoteMessage) => {
RNCallKeep.setup(options);
RNCallKeep.setAvailable(true);

try {
//Converting the message payload into CometChat Message.
let msg = CometChat.CometChatHelper.processMessage(
JSON.parse(remoteMessage.data.message)
);
if (msg.category == 'call') {
//need to check if the notification we received for Call initiated or ended
if (msg.action == 'initiated') {
CallKeepHelper.msg = msg; //setting the msg object in call keep helper class
CallKeepHelper.displayCallAndroid(); //this method is used to display incoming calls in android t
} else {
//if sender cancels the call before receiver accept or reject call then we also need to stop our notification
RNCallKeep.endCall(msg.conversationId);
}
}
} catch (e) {
console.log(e);
}
});

IOS

In IOS we use APNs push and voip push notification to display push notification and display call CallKit for calls. The notification are handled in Native IOS You need to add the code in AppDelegate.m file to display CallKit

//add this import at the top or the file
#import "RNCallKeep.h"
#import "RNFBMessagingModule.h"
#import <PushKit/PushKit.h>
#import "RNVoipPushNotificationManager.h"




_* <------ add this function *_
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
// Register VoIP push token (a property of PKPushCredentials) with server

[RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type];
}

- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type
{
// --- The system calls this method when a previously provided push token is no longer valid for use. No action is necessary on your part to re-register the push type. Instead, use this method to notify your server not to send push notifications using the matching push token.
}

// --- Handle incoming pushes
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {

// --- NOTE: apple forced us to invoke callkit ASAP when we receive voip push
// --- see: react-native-callkeep

// --- Retrieve information from your voip push payload
NSDictionary *content = [payload.dictionaryPayload valueForKey:@"aps"];
NSDictionary *sender = [content valueForKey:@"alert"];
NSString *uuid =[[[NSUUID UUID] UUIDString] lowercaseString];
NSString *callerName=[sender valueForKey:@"title"];
NSString *handle = [sender valueForKey:@"title"];

// --- Process the received push
[RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];

[RNCallKeep reportNewIncomingCall: uuid
handle: handle
handleType: @"generic"
hasVideo: NO
localizedCallerName: callerName
supportsHolding: YES
supportsDTMF: YES
supportsGrouping: YES
supportsUngrouping: YES
fromPushKit: YES
payload: nil
withCompletionHandler: completion];

}