Jump to top

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
          // Note: iOS frequently will receive the message but decline to deliver it to your app.
          //           This is an Apple design choice to favor user battery life over data-only delivery
          //           reliability. It is not under app control, though you may see the behavior in device logs.
          '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 and content-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.