civil-and-structural-engineering
Implementing Real-time Notifications with Firebase Cloud Messaging in React Native
Table of Contents
Why Real-Time Notifications Matter
Real-time notifications are a cornerstone of modern mobile engagement. They keep users informed of time-sensitive updates—new messages, order confirmations, breaking news, or social interactions—without requiring them to open your app. By implementing push notifications, you can increase retention rates, reduce churn, and create a more responsive user experience. Firebase Cloud Messaging (FCM) is one of the most reliable and cost-effective solutions, offering a unified way to send notifications across Android, iOS, and web platforms. In this guide, you will learn step-by-step how to integrate FCM into your React Native project, from initial setup to sending targeted messages, with production-ready code and best practices.
Why Firebase Cloud Messaging?
FCM provides several advantages for React Native developers:
- Cross-platform consistency – A single API works for Android and iOS, reducing code duplication.
- Scalable infrastructure – Google’s infrastructure handles millions of messages with low latency.
- Rich payloads – Send notification messages, data-only messages, or a combination of both.
- Topic and device group messaging – Target specific user segments or broadcast to all users.
- Free tier – FCM is free, with generous limits suitable for most apps.
When combined with @react-native-firebase/messaging, you get native-level performance and access to all FCM features without writing platform-specific code.
Prerequisites
Before you begin, ensure you have the following:
- A React Native project (bare workflow recommended; Expo managed workflow requires additional steps).
- A Firebase Console account.
- Node.js and npm (or yarn) installed.
- For Android: Android Studio, a physical device or emulator with Google Play Services.
- For iOS: Xcode 14+, a physical device (simulator does not support push unless using a special build), and an Apple Developer account.
Setting Up Your Firebase Project
Create a Firebase Project
Go to the Firebase Console, click Add project, and follow the steps. Once your project is ready, add an Android app and an iOS app to it.
Add Your Android App
Register your Android app in Firebase with the package name (e.g., com.yourcompany.yourapp). Download the google-services.json file and place it in your React Native project at android/app/google-services.json. Next, update your android/build.gradle (project-level) and android/app/build.gradle as per React Native Firebase documentation:
// android/build.gradle (project-level)
dependencies {
classpath("com.google.gms:google-services:4.4.0")
}
// android/app/build.gradle
apply plugin: 'com.google.gms.google-services'
Then, ensure you declare the default Firebase channel and notification icon in AndroidManifest.xml:
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="default" />
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/ic_notification" />
Add Your iOS App
In the Firebase Console, add an iOS app with your bundle identifier (e.g., com.yourcompany.yourapp). Download GoogleService-Info.plist and add it to your Xcode project. Open Xcode, right-click on your project, and choose Add Files to "YourProject". Ensure the file is added to the main target.
Next, enable push notifications capability: in Xcode, select your target → Signing & Capabilities → + → Push Notifications. Also, you must upload an APNs authentication key in the Firebase Console under Cloud Messaging → APNs Authentication Key, or use APNs certificates if you prefer.
Installing and Configuring the Messaging Package
First, install the core Firebase app module and the messaging module:
npm install @react-native-firebase/app @react-native-firebase/messaging
Then rebuild your app on each platform:
npx react-native run-android # Android
cd ios && pod install && cd .. # iOS (required after adding native modules)
After installation, initialize Firebase (if not auto-initialized) by adding this to your root component or entry file:
import { firebase } from '@react-native-firebase/app';
if (!firebase.apps.length) {
firebase.initializeApp();
// optionally pass config from environment variables
}
On iOS only, Firebase auto-initialization uses GoogleService-Info.plist; you usually don't need explicit initializeApp. However, for Android you may need it if you have multiple projects or want to control initialization order.
Requesting Notification Permissions
iOS requires explicit user permission. Android 13+ also requires runtime permission for notifications. The @react-native-firebase/messaging library handles this easily:
import messaging from '@react-native-firebase/messaging';
import { Platform, Alert } from 'react-native';
async function requestUserPermission() {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Notification permission granted:', authStatus);
} else {
console.log('Notification permission denied');
// Optionally show a dialog explaining why notifications are needed
}
}
// Call on app start (only on iOS or Android 13+)
if (Platform.OS === 'ios' || (Platform.OS === 'android' && Platform.Version >= 33)) {
requestUserPermission();
}
On Android 12 and below, no permission is required for local notifications, but FCM will still work. You may also want to check permission status later using messaging().hasPermission().
Handling Incoming Notifications
Foreground Messages
When your app is in the foreground, FCM delivers messages via the onMessage listener. By default, these are not displayed as system notifications—you must show them yourself (e.g., using @notifee/react-native for local notifications).
import messaging from '@react-native-firebase/messaging';
useEffect(() => {
const unsubscribe = messaging().onMessage(async remoteMessage => {
console.log('Foreground message:', remoteMessage);
// Example: show a local notification using Notifee
// await notifee.displayNotification(remoteMessage.notification);
});
return unsubscribe;
}, []);
Background Messages (iOS and Android)
When the app is in the background (paused or killed), FCM automatically displays a system notification if the payload contains a notification key. To handle data-only payloads in background, you need to register a background handler:
import messaging from '@react-native-firebase/messaging';
// Must be registered outside of any component
messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log('Background message:', remoteMessage);
// Perform background operations (e.g., sync data, update badge)
});
This handler will be invoked even if the app is killed (on Android) or in the background (iOS). Note that iOS has strict background execution limits; keep the processing brief.
On App Opened from Notification
To handle a user tapping a notification, use onNotificationOpenedApp (for background start) and getInitialNotification (for killed state):
import messaging from '@react-native-firebase/messaging';
// When app is in background and notification is tapped
useEffect(() => {
const unsubscribe = messaging().onNotificationOpenedApp(remoteMessage => {
console.log('Notification opened app from background:', remoteMessage);
// Navigate to specific screen based on data
});
return unsubscribe;
}, []);
// When app is killed and opened by notification
useEffect(() => {
messaging().getInitialNotification().then(remoteMessage => {
if (remoteMessage) {
console.log('Notification opened app from killed state:', remoteMessage);
// Navigate accordingly
}
});
}, []);
Device Token Management
Your server needs a device's FCM token to send targeted messages. Retrieve it after permission is granted:
const getToken = async () => {
try {
const token = await messaging().getToken();
console.log('FCM Token:', token);
// Send token to your backend and store it
await saveTokenToServer(token);
} catch (error) {
console.error('Failed to get token:', error);
}
};
// Call after permissions granted
if (enabled) {
getToken();
}
Tokens can change (e.g., after app reinstall, clearing data, or on iOS when notification settings change). Listen for token refresh:
useEffect(() => {
const unsubscribe = messaging().onTokenRefresh(token => {
console.log('Token refreshed:', token);
// Update your backend
});
return unsubscribe;
}, []);
On Android, token may also be fetched using messaging().getToken() at any time. Apple recommends using onTokenRefresh as the primary mechanism.
Sending Notifications
Using Firebase Console (for testing)
In the Firebase Console, go to Cloud Messaging → Send your first message. Enter notification title and body, then under Target choose Single Device and paste the FCM token you retrieved. Set your app name and send. You should receive the notification on your device.
Sending via FCM HTTP v1 API (Recommended for Production)
Google recommends using the HTTP v1 API over the legacy legacy API. It supports finer access control via OAuth2 and is more secure. Here's a Node.js example using a service account:
const admin = require('firebase-admin');
const serviceAccount = require('./path/to/serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
const message = {
token: 'DEVICE_FCM_TOKEN',
notification: {
title: 'Hello!',
body: 'This is a test notification from v1 API.',
},
// optional: android, apns config
android: {
notification: {
channel_id: 'default',
priority: 'high',
},
},
apns: {
payload: {
aps: {
alert: {
title: 'Hello!',
body: 'This is a test notification from v1 API.',
},
sound: 'default',
},
},
},
};
admin.messaging().send(message)
.then(response => {
console.log('Message sent:', response);
})
.catch(error => {
console.error('Error sending message:', error);
});
Obtain a server key via Firebase Console → Project Settings → Service accounts; generate a new private key and download the JSON file. Keep it secure and never expose it client-side.
Data-Only Messages
To send a payload that does not trigger a system notification (useful for silent sync), omit the notification key and use only data:
{
"token": "DEVICE_FCM_TOKEN",
"data": {
"type": "new_message",
"message_id": "1234"
}
}
These messages are delivered to both foreground and background handlers. On iOS, background data messages require content-available: 1 in the APNs payload and the app must have appropriate background modes enabled.
Advanced: Topic Subscriptions
Instead of sending to a single token, you can subscribe devices to topics and broadcast to all subscribers.
// Subscribe to topic
await messaging().subscribeToTopic('promotions');
// Unsubscribe
await messaging().unsubscribeFromTopic('promotions');
Then send a message to the topic from the server:
admin.messaging().send({
topic: 'promotions',
notification: { title: 'Big Sale!', body: '40% off everything.' },
});
Topics are public; anyone can subscribe. For targeted groups, use device groups or condition-based expressions (e.g., "'TopicA' in topics && ('TopicB' in topics)").
Best Practices
Define Notification Channels (Android)
Android 8+ requires notification channels. Create them in your app:
import notifee, { AndroidImportance } from '@notifee/react-native';
async function createChannel() {
await notifee.createChannel({
id: 'default',
name: 'Default Notifications',
importance: AndroidImportance.HIGH,
});
}
Then specify the channel in your server payload under android.notification.channel_id.
Respect User Opt-In
Do not request permission on first launch without explaining the value. Show a custom dialog first: "We will notify you when you receive a new message." Then call requestPermission(). On iOS, you can also call requestPermission({provisional: true}) to present notifications quietly initially, then upgrade later.
Use Analytics to Optimize
Enable Firebase Analytics to track notification opens, conversions, and user engagement. Add the Analytics package:
npm install @react-native-firebase/analytics
Then log events when a notification is opened:
import analytics from '@react-native-firebase/analytics';
// Inside notification handler
await analytics().logEvent('notification_opened', {
notification_title: remoteMessage.notification?.title,
});
Handle Token Expiration Gracefully
When a token becomes invalid (e.g., user uninstalls/reinstalls), your server will receive an error. Implement a mechanism to remove stale tokens from your database. For example, when sending returns "errorCode": "UNREGISTERED", delete the token.
Troubleshooting Common Issues
- Notifications not received on iOS: Ensure APNs key/certificate is uploaded in Firebase Console and that your app bundle ID matches. Test on a physical device.
- Foreground notifications not showing: By design, FCM doesn't display foreground notifications. Use a local notification library like
notifeeorreact-native-push-notificationto show them. - Token undefined on iOS simulator: Simulators cannot receive push notifications. Use a real device or a remote push simulator tool.
- Android app crashes on startup after adding FCM: Check
google-services.jsonlocation and ensure you have the correct Gradle dependency versions. Also, verify that yourapplicationIdmatches the package in Firebase. - Background data messages not delivered on iOS: Enable Remote notifications in Xcode capabilities and set the
content-available: 1flag in your server payload. Also, your background handler must complete quickly (iOS may throttle).
Conclusion
Integrating Firebase Cloud Messaging into your React Native app equips you with a powerful, scalable notification system that works seamlessly on both Android and iOS. By following the steps outlined—project setup, platform-specific configuration, permission handling, token management, and server-side sending—you can deliver real-time, engaging notifications to your users. Leverage topics for mass messaging, use data-only payloads for silent updates, and always respect user preferences. With proper implementation, FCM will become an invaluable asset for boosting user retention and driving app engagement.
For further reference, consult the official React Native Firebase Messaging documentation and the FCM documentation.