Server Integration
Integrate Firebase Cloud Messaging with your backend server.
The Cloud Messaging module provides the tools required to enable you to send custom messages directly from your own servers. For example, you could send a FCM message to a specific device when a new chat message is saved to your database and display a notification or update local device storage so the message is instantly available.
Firebase provides a number of SDKs in different languages such as Node.JS, Java, Python, C# and Go. It also supports sending messages over HTTP. These methods allow you to send messages directly to your user's devices via the FCM servers.
Device tokens
To send a message to a device, you must access its unique token. A token is automatically generated by the device and can be accessed using the Cloud Messaging module. The token should be saved inside of your systems data-store and should be easily accessible when required.
The examples below use a Cloud Firestore database to store and manage the tokens, and Authentication to manage the users identity. You can however use any datastore or authentication method of your choice.
If using iOS, ensure you have read and followed the steps in registered with FCM and requested user permission before trying to receive messages!
Saving tokens
Once your application has started, you can call the getToken
method on the Cloud Messaging module to get the unique
device token (if using a different push notification provider, such as Amazon SNS, you will need to call getAPNSToken
on iOS):
import React, { useEffect } from 'react';
import messaging from '@react-native-firebase/messaging';
import auth from '@react-native-firebase/auth';
import firestore from '@react-native-firebase/firestore';
import { Platform } from 'react-native';
async function saveTokenToDatabase(token) {
// Assume user is already signed in
const userId = auth().currentUser.uid;
// Add the token to the users datastore
await firestore()
.collection('users')
.doc(userId)
.update({
tokens: firestore.FieldValue.arrayUnion(token),
});
}
function App() {
useEffect(() => {
// Get the device token
messaging()
.getToken()
.then(token => {
return saveTokenToDatabase(token);
});
// If using other push notification providers (ie Amazon SNS, etc)
// you may need to get the APNs token instead for iOS:
// if(Platform.OS == 'ios') { messaging().getAPNSToken().then(token => { return saveTokenToDatabase(token); }); }
// Listen to whether the token changes
return messaging().onTokenRefresh(token => {
saveTokenToDatabase(token);
});
}, []);
}
The above code snippet has a single purpose; storing the device FCM token on a remote database. The useEffect
is fired
when the App
component runs and immediately gets the token. It also listens to any events when the device automatically refreshes
the token.
Inside of the saveTokenToDatabase
method, we store the token on a record specifically relating to the current user. You may also
notice that the token is being added via the FieldValue.arrayUnion
method. A user can have more than one token (for example using 2 devices)
so it's important to ensure that we store all tokens in the database.
Using tokens
With the tokens stored in a secure datastore, we now have the ability to send messages via FCM to those devices.
The following example uses the Node.JS
firebase-admin
package to send messages to our devices, however any SDK (listed above) can be used.
Go ahead and setup the firebase-tools
library on your server environment.
Once setup, our script needs to perform two actions:
- Fetch the tokens required to send the message.
- Send a data payload to the devices that the tokens are registered to.
Imagine our application being similar to Instagram. Users are able to upload pictures, and other users can "like" those pictures. Each time a post is liked, we want to send a message to the user that uploaded the picture. The code below simulates a function which is called with all of the information required when a picture is liked:
// Node.js
var admin = require('firebase-admin');
// ownerId - who owns the picture someone liked
// userId - id of the user who liked the picture
// picture - metadata about the picture
async function onUserPictureLiked(ownerId, userId, picture) {
// Get the owners details
const owner = admin.firestore().collection('users').doc(ownerId).get();
// Get the users details
const user = admin.firestore().collection('users').doc(userId).get();
await admin.messaging().sendEachForMulticast({
tokens: owner.tokens, // ['token_1', 'token_2', ...]
data: {
owner: JSON.stringify(owner),
user: JSON.stringify(user),
picture: JSON.stringify(picture),
},
apns: {
payload: {
aps: {
// Required for background/quit data-only messages on iOS
'content-available': true,
// Required for background/quit data-only messages on Android
priority: 'high',
},
},
},
});
}
Data-only messages are sent as low priority on both Android and iOS and will not trigger the setBackgroundMessageHandler
by default. To enable this functionality, you must set the "priority" to high
on Android and enable the
content-available
flag for iOS in the message payload.
If using the FCM REST API, see the following documentation on setting
priority
andcontent-available
!
The data
property can send an object of key-value pairs totaling 4KB
as string values (hence the JSON.stringify
).
Back within our application, as explained in the Usage documentation, our message handlers will receive a
RemoteMessage
payload containing the message details sent from the server:
function App() {
useEffect(() => {
const unsubscribe = messaging().onMessage(async remoteMessage => {
const owner = JSON.parse(remoteMessage.data.owner);
const user = JSON.parse(remoteMessage.data.user);
const picture = JSON.parse(remoteMessage.data.picture);
console.log(`The user "${user.name}" liked your picture "${picture.name}"`);
});
return unsubscribe;
}, []);
}
Your application code can then handle messages as you see fit; updating local cache, displaying a notification or updating UI. The possibilities are endless!
Signing out users
Firebase Cloud Messaging tokens are associated with the instance of the installed app. By default, only token expiration or uninstalling/reinstalling the app will generate a fresh token.
This means that by default, if your app has users and you allow them to log out and log in on the same app on the same device, the same FCM token will be used for both users. Usually this is not what you want, so you must take care to cycle the FCM token at the same time you handle user logout/login.
How and when you invalidate a token and generate a new one will be specific to your project, but a common pattern is to delete the FCM token during logout and update your back end to remove it, then to fetch the FCM token during login and update your back end systems to associate the new token with the logged in user.
https://rnfirebase.io/reference/messaging#deleteToken https://rnfirebase.io/reference/messaging#getToken
Note that when a token is deleted by calling the deleteToken
method, it is immediately and permanently invalid.
Send messages to topics
When devices subscribe to topics, you can send messages without specifying/storing any device tokens.
Using the firebase-admin
Admin SDK as an example, we can send a message to devices subscribed to a topic:
const admin = require('firebase-admin');
const message = {
data: {
type: 'warning',
content: 'A new weather warning has been created!',
},
topic: 'weather',
};
admin
.messaging()
.send(message)
.then(response => {
console.log('Successfully sent message:', response);
})
.catch(error => {
console.log('Error sending message:', error);
});
Conditional topics
To send a message to a combination of topics, specify a condition, which is a boolean expression that specifies the target
topics. For example, the following condition will send messages to devices that are subscribed to weather
and either news
or traffic
:
condition: "'weather' in topics && ('news' in topics || 'traffic' in topics)"
To send a message to this condition, replace the topic
key with condition
:
const admin = require('firebase-admin');
const message = {
data: {
content: 'New updates are available!',
},
condition: "'weather' in topics && ('news' in topics || 'traffic' in topics)",
};
admin
.messaging()
.send(message)
.then(response => {
console.log('Successfully sent message:', response);
})
.catch(error => {
console.log('Error sending message:', error);
});
Send messages with image
Both the Notifications composer and the FCM API support image links in the message payload.
iOS
To successfully send an image using the Admin SDK it's important that the ApnsConfig
options are set:
const payload = {
notification: {
body: 'This is an FCM notification that displays an image!',
title: 'FCM Notification',
},
apns: {
payload: {
aps: {
'mutable-content': 1, // 1 or true
},
},
fcmOptions: {
imageUrl: 'image-url',
},
},
};
Check out the official Firebase documentation to see the list of available configuration for iOS.
Android
Similarly to iOS, some configurations specific to Android are needed:
const payload = {
notification: {
body: 'This is an FCM notification that displays an image!',
title: 'FCM Notification',
},
android: {
notification: {
imageUrl: 'image-url',
},
},
};
If you want to know more about sending an image on Android have a look at the documentation.
Pulling it all together
It's possible to send one notification that will be delivered to both platforms using the Admin SDK:
const admin = require('firebase-admin');
// Create a list containing up to 500 registration tokens.
// These registration tokens come from the client FCM SDKs.
const registrationTokens = ['YOUR_REGISTRATION_TOKEN_1', 'YOUR_REGISTRATION_TOKEN_2'];
const message = {
tokens: registrationTokens,
notification: {
body: 'This is an FCM notification that displays an image!',
title: 'FCM Notification',
},
apns: {
payload: {
aps: {
'mutable-content': 1,
},
},
fcmOptions: {
imageUrl: 'image-url',
},
},
android: {
notification: {
imageUrl: 'image-url',
},
},
};
admin
.messaging()
.sendEachForMulticast(message)
.then(response => {
console.log('Successfully sent message:', response);
})
.catch(error => {
console.log('Error sending message:', error);
});
If you want to read more about building send requests with the Admin SDK check out this link.