Part 1: Push Notifications in React Native 2024
I recently had a chance to deep dive into Push notifications at my workplace and learned a lot of things. I had to start with what is push notification. But in the end, I was able to successfully build a Push notification service end to end.
I have poured all the knowledge I have combined from various articles, YouTube videos, blogs, official docs, tips and tricks, bugs, and solutions into this article. Hence stick with me through the end and you will learn what you seek!
There are a lot of blogs and articles about implementing Push notifications in react native. But they are pretty old and include outdated code and libraries. There have been a lot of improvements in libraries. But someone who is a complete beginner will find it very difficult to gather all the content together and build a full-fledged Push Notification service in React native.
Push notification looks easy. But believe me when I say this, you shouldn’t underestimate this if this is something you will be working first time at your workplace. Because Apple and Android are adding improvements and features at a very fast pace and it's pretty difficult to keep up with it. This is something I learned after actually working on developing it end to end. I used to try to learn this by reading documentation. I couldn’t. You have to get into the practical real-world use case to learn it end to end.
So let’s start.
You must have seen these Zomato and Swiggy (food delivery apps in India) notifications… these are called push notifications…
If you notice the device shows this notification even if the app is background (app still in RAM but not quit) or quit. Also, they appear only when your device is connected to the internet. If the device is offline, they are queued until the device comes online. In this article, we will learn how they come up and behind the scenes of how they reach from server to your device.
First, let's start from the basics. I believe in providing comprehensive explanations. I think that even the absolute beginner who doesn’t know anything about notifications should be able to understand the article.
There are various libraries out there for implementing Push Notification service in React Native.
Existing libraries and updates on them as of now (29th Sept 2024):
- react-native-push-notifications: https://github.com/zo0r/react-native-push-notification#readme. This is not actively maintained now. See this https://github.com/zo0r/react-native-push-notification?tab=readme-ov-file#state-of-the-repository
- react-native-push-notification/ios: https://github.com/react-native-push-notification/ios. React Native Push Notification API only for iOS. You have to find different library for Android. This was originally provided by react-native in its core package. But later moved to the community package.
- react-native-firebase: https://rnfirebase.io/. Provided by Firebase one of the core products of Google. They have the FCM notification service. https://rnfirebase.io/messaging/usage is used here for notification cloud messaging and is open source. It can handle both ios and Android notifications using common functions.
- react-native-notifications : https://github.com/wix/react-native-notifications. Provided by Wix. More reliable than react-native-push-notifications and react-native-push-notification/ios. It can handle both iOS and Android notifications using common functions.
In this article series, we will be completely building a push notification service on frontend using Firebase. I have divided the explanation into two articles.
Here are the steps covered in this article:
Step 1: What is a Remote Push notification?
Step 2: What is a device token?
Step 3: How it works behind the scenes: Using FCM service to send notifications to both Android and IOS apps
Step 4: How it works behind the scenes: Using FCM service to send notifications to Android and APN service to send notifications to IOS apps
Step 5: FCM SDK integration on UI
Step 6: Asking Notification Permissions
Step 7: IOS: Register for push notifications and get a token
Types of notifications: Remote Push Notifications and Local Notifications.
Remote Push notification: This is called a remote notification because they are pushed from the remote server with the help of one of the push notification services. Yes exactly! The personalized notifications you get from Zomato/Swiggy (Online food delivery apps in India) are of the same type.
The notification you get on your device is handled/sent by one of the two notification services: FCM (Firebase Cloud Messaging — Google product ) and APN (Apple Push notification — Apple product).
Backend can choose one of the following paths to send the notification:
1. Use FCM service to send notifications to both Android and IOS apps.
2. Use the FCM service to send notifications to the Android app and the APN service to send notifications to the IOS app.
Device Token: Every device is assigned a unique token by the push notification services. This token is used to uniquely identify devices for targeting notifications.
Use FCM service to send notifications to both Android and IOS apps:
• Android and IOS apps must be configured to use the FCM SDK. In React Native, a popular library for integrating FCM is react-native-firebase, which simplifies the integration process.
• After the app launches, the Android and IOS apps need to retrieve a device token, referred to as the FCM token using methods provided by the FCM SDK. FCM tokens are uniquely different for both Android and IOS apps. In the case of Android, FCM SDK directly gives us an FCM token through FCM server. But in the case of IOS, the FCM SDK call first reaches to APN and asks for an APN token. After FCM has an APN token for that device, it creates an equivalent FCM token for that APN token and stores this pair of APN token and equivalent FCM token in a table. But it retunes only FCM token when you ask it.
• This FCM token is then sent to the backend through an API for future use in sending notifications.
• The backend server can register the FCM token and integrate it with the Firebase Admin SDK (e.g., using firebase-admin for Node.js). Firebase Admin SDK is provided by Firebase to send the notification payload to their servers.
• When a notification needs to be sent, the backend sends the notification payload, along with the FCM token, to the FCM server using the Firebase Admin SDK. The FCM server then generates a message ID.
• Now there is a difference in how it FCM server handles the notification payload further depending on the device.
For Android:
• If the device is online, the FCM server sends the message immediately. If the device is offline, the message is queued until the device comes online.
• There is a constant line of connection between the FCM server and your Android app. Specifically, we can say FCM servers have persistent websocket connection with play services on your device. When the socket connection breaks in case of no internet or any other issue, it automatically tries to reconnect once the device is online. Note that this connection is open but inactive, waiting for something to happen. So battery usage is negligible.
• Websocket connection is shared among all the installed apps in the device and whenever a notification comes from the FCM server for any app, it follows the same line. If a new FCM SDK-enabled app is installed, it just joins this exciting connection.
• FCM server sends notification payload through this websocket connection to the play services software. When a message arrives, Play services can wake the phone in the background for some time (if the app is quit) and deliver the message to the app it’s meant for.
• The FCM SDK on the app receives a signal when a message is available. Then it accepts the received message and displays a notification to the user along with the payload. Also if it's a data-only notification, it can be handled by FCM SDK.
For IOS:
• FCM server doesn’t have any direction connection with IOS devices. Only the APN service has that access. Hence FCM server now needs to be integrated to hand over the notification payload to the APN service so that the APN service can further send the notification to IOS devices. Note that it already has a mapping of FCM token to APN token stored in the table. Hence once the backend server provides FCM token to the FCM server, it fetches equivalent APN token and passes it in payload.
• To establish a connection between the FCM server and APN service, you need to link FCM with APNs. This requires you to create an APN key (or certificate) from your Apple Developer account and upload it to Firebase Project. This setup establishes the connection between FCM and APNs. Check this out: https://rnfirebase.io/messaging/usage/ios-setup#linking-apns-with-fcm-ios
• APNs Key: The APNs key (or certificate) is what allows Firebase/FCM to authenticate and communicate with Apple’s APNs service on behalf of your iOS app. It authorizes FCM to send push notifications payload to APNs.
• Once FCM forwards the notification to APNs using the APNs key you’ve set up in Firebase, APNs deliver the notification to the iOS device. APNs maintain their persistent connection with every iOS device (via the APNs token).
• When a message arrives, iOS can wake the app in the background for some time (if the app is quit) and deliver the message to the app it’s meant for.
• The FCM SDK on the app receives a signal when a message is available. Then it accepts the received message and displays a notification to the user along with the payload. Also if it’s a data-only notification, it can be handled by FCM SDK.
Use FCM service to send notifications to Android and APN service to send notifications to IOS apps:
Everything is the same as above with some differences:
1. In the previous case, we registered FCM tokens for both Android and IOS apps. In this case token that needs to be sent to the backend needs to be an FCM token in the case of Android and an APN token in the case of IOS.
2. Here FCM doesn’t need to be linked to APN service. Backend can directly integrate the APN and send the notification payload to the APN servers which can then deliver the notification to IOS devices.
FCM SDK integration:
Follow these steps to integrate Firebase UI SDK in Android and IOS:
Note: The following steps are as of Sept 2024. In the Future steps may differ. Hence it's a good idea to follow official documentation.
- Go here https://rnfirebase.io/.
Create a Firebase project.
Create Android and IOS apps in the Firebase project.
Install yarn/npm package.
Also make sure to add google-services.json file for android and GoogleService-Info.plist for ios in project.
Follow remaining steps mentioned in the documentation. - Head over to https://rnfirebase.io/messaging/usage.
Install messaging yarn package from Firebase messaging.
For IOS, only a pod install is not enough. You need to follow the steps mentioned here also: https://rnfirebase.io/messaging/usage/ios-setup
Asking Permissions:
You can use react-native-permissions to ask for Notification Permission from the user. Go here https://github.com/zoontek/react-native-permissions?tab=readme-ov-file#readme and perform the necessary steps for installation. Make sure to follow and rely on the official doc as it may have any additional steps further.
Make sure you have the following enabled in Podfile:
setup_permissions([
'Notifications',
])
For Android, make sure to add the following permission in AndroidManifest.xml.
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Android 12 and lower: After declaring permission, apps are automatically granted permission to send notifications. To stop receiving notifications, users have to manually disable notifications from app settings.
For IOS, Android 13 and higher OS versions: You need to request this permission runtime also. Declaring permissions is not enough. Android categorizes this permission as Runtime dangerous permission. Hence here the apps should request permission from users at runtime before sending notifications.
You can use this helper function to ask for notification permission at the right place in your app.
import {
RESULTS,
requestNotifications,
} from 'react-native-permissions';
export const isIos = () => Platform.OS === 'ios';
export const isAndroid = () => Platform.OS === 'android';
export const getPlatformVersion = () => Number(Platform.Version);
export const requestNotificationsPermission = (onGranted: () => void, onBlocked?: () => void) => {
requestNotifications(['alert', 'sound', 'badge']).then(({ status }) => {
if (status === RESULTS.GRANTED) {
onGranted();
} else {
onBlocked();
}
});
};
useEffect(() => {
if(isIos() || (isAndroid() && getPlatformVersion() >= 33)){
requestNotificationsPermission(() => {
//notification granted tasks
}, () => {
//notification denied tasks
});
}
}, [])
Before calling requestNotifications, make sure you have targeted at least SDK 33 in the build.gradle as per https://github.com/zoontek/react-native-permissions/releases/tag/3.5.0
IOS notification Permission:
Android 13+ notification permission:
Open app settings: If the user accepts and allows permission, you can start sending permission. If a user denies permission, afterward you can give a CTA to the user to allow permissions and you can navigate the user to app notification settings. This is a helper function for that: https://github.com/zoontek/react-native-permissions?tab=readme-ov-file#opensettings
You can store the notification permission status in redux and whenever notification permission is allowed, you can execute the following tasks:
IOS: Register for push notifications and get a token
App.tsx
import messaging from '@react-native-firebase/messaging';
import { setNotificationsHandler } from '../helpers/notificationsHelper';
setNotificationsHandler();
const App = () => {
const [headLess, setIsHeadless] = useState(Platform.OS === 'ios' ? true : false);
useEffect(() => {
SplashScreen.hide();
if (isIos()) {
messaging()
.getIsHeadless()
.then(isHeadless => {
setIsHeadless(isHeadless);
});
}
}, []);
return headLess ? null : <AppNavgation/>
};
export default App;
notificationsHelper.ts
import messaging from '@react-native-firebase/messaging';
export const setNotificationsHandler = () => {
await messaging().registerDeviceForRemoteMessages();
}
Make sure to call the notification handler outside of React Component inside app.tsx.
Now as per https://rnfirebase.io/messaging/usage#background-application-state, you can implement iOS-specific headless code. This is needed because: When a notification is received on IOS, the iOS device silently wakes up the device and enters headless mode. In this case, it also runs any side-effects mentioned in the react context. To avoid running any side-effects in headless mode, we added a check of isHeadless.
Inside setNotificationsHandler, after registering for remote messages for IOS, get a device token.
If you need an FCM token for Android and IOS devices, you can use this helper function as per https://rnfirebase.io/messaging/notifications#getting-a-device-token
notificationsHelper.ts
import messaging from '@react-native-firebase/messaging';
export const setNotificationsHandler = async () => {
await messaging().registerDeviceForRemoteMessages();
const token = await messaging().getToken();
await passTokenToBackend(token);
}
You can call API to send the token to the backend. In case you need to have an APN token for IOS devices, you can use
const token = await messaging().getAPNSToken();
Also after the token is refreshed, you need to get a refreshed token and pass it to the API.
notificationsHelper.ts
import messaging from '@react-native-firebase/messaging';
export const setNotificationsHandler = async () => {
await messaging().registerDeviceForRemoteMessages();
const token = await messaging().getToken();
await passTokenToBackend(token);
messaging().onTokenRefresh((token) => {
//call api and pass the token
passTokenToBackend(token)
});
}
You should destroy the token every time the user logs out from the current session.
Signin/signup/intro screens.tsx
useEffect(() => {
messaging().deleteToken();
}, []);
Also before doing any action, you can use the following helper function to check if the user has granted notification permission or not. If granted then you can go ahead. If not granted, you can skip this process.
Write this somewhere in your helper functions:
import messaging from '@react-native-firebase/messaging';
export const checkNotificationPermissionStatus = (): Promise<boolean> => {
return new Promise(async (resolve, reject) => {
return messaging()
.hasPermission()
.then(enabled => {
let granted =
enabled === messaging.AuthorizationStatus.AUTHORIZED ||
enabled === messaging.AuthorizationStatus.PROVISIONAL;
return resolve(granted);
})
.catch(error => reject(error));
});
};
Then use this in the notifications handler.
notificationsHelper.ts
import messaging from '@react-native-firebase/messaging';
export const setNotificationsHandler = async () => {
let granted = await checkNotificationPermissionStatus();
if(!granted) return;
await messaging().registerDeviceForRemoteMessages();
const token = await messaging().getToken();
await passTokenToBackend(token);
messaging().onTokenRefresh((token) => {
//call api and pass the token
passTokenToBackend(token)
});
}
Now we have done the basic configurations and now backend can go ahead and send the push notification to the user. With our basic setup, the device will receive push notifications.
There can be three states of the device while notification is received.
- Foreground: When a user is using the app and the app is in the foreground. In this state, by default notification is not shown.
- Background: When a user is not using the app and the app has been set to background in RAM (minimized). This typically occurs when the user has pressed the “home” button on the device or has switched to another app via the app switcher. But still, the app is active in the background and not killed. In this state, a notification is shown in the notification drawer.
- Killed/quit state: When the user has killed the app completely or the User may have put the app in the background but after some time OS may have suspended the app from the background to free up memory/RAM. In this state, a notification is shown in the notification drawer.
Now in all the above states, if we need to handle the notification data after the notification is shown and also do some actions on notification tap after the user taps on certain notifications, firebase provides some event handlers for this. Let’s handle this in the next article.
Here’s second part: https://medium.com/@varunkukade999/part-2-push-notifications-in-react-native-2024-479ec2b87a89
Let me know if you have any doubts in the comments. If you face any specific problem, let everyone know in the comments.
If you found this tutorial helpful, don’t forget to give this post 50 claps👏 and follow 🚀 if you enjoyed this post and want to see more. Your enthusiasm and support fuel my passion for sharing knowledge in the tech community.
I create various tech blogs in my leisure time. You can find more of such articles on my profile -> https://medium.com/@varunkukade999