Firebase Cloud Messaging for Push Notifications

I am going to show you how to use Firebase Cloud Messaging for Push Notifications implementation on Android.

What will you learn after reading this article?

  • What is FCM.
  • How to configure your android application to be able to receive FCM notifications.
  • How to send FCM notification from Firebase Console.
  • What kinds of cloud messaging does Firebase have. What the difference between them?
  • How to customize android status bar notification for FCM message?
  • Two methods of how to organize user2user notification sending through FCM:
    • Example of FCM implementation with own FCM App server.
    • Example of FCM implementation without FCM App server (using only android clients).
  • Example of FCM in real messenger-application. You will be able to play with the app and to explore its code.

Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that lets you reliably deliver messages at no cost. According to Firebase Official Documentation:

  • FCM is the new version of GCM under the Firebase brand.
  • It inherits GCM’s core infrastructure, with new SDKs to make Cloud Messaging development easier.

Benefits of upgrading from GCM to FCM SDK include:

  • Simpler client development. You no longer have to write your own registration or subscription retry logic.
  • An out-of-the-box notification solution. You can use Firebase Notifications, a serverless notifications solution with a web console that lets anyone send notifications to target specific audiences based on Firebase Analytics insights.

To set up a Firebase Cloud Messaging Client App on Android, follow this official instruction:

https://firebase.google.com/docs/cloud-messaging/android/client

Once setup FCM on android will be finished, you can test if it works well with sending test notification from Firebase Console. To send notifications from the Notifications console go to Firebase Console, then open your project, then open the Notifications console and start sending notifications to user segments.

Firebase provides Firebase Notifications and Firebase Cloud Messaging services. For both of them your android application needs to be configured the same way. So what is the difference between these two features? To get answer on this question please refer to the official documentation.

https://firebase.google.com/support/faq/#notifications-difference

So, from above comparison we can see that we can use Firebase Notifications only with Notifications UI tool embedded into Firebase console. But we want to send user2user notifications, right?

Let's see deeper into Firebase Cloud Messaging

Firebase Cloud messaging allows you to subscribe to receive messages and also provides great flexible API called “Firebase Cloud Messaging HTTP Protocol” for generating and sending messages. Please refer to official documentation to read about this API:

https://firebase.google.com/docs/cloud-messaging/http-server-ref

To send FCM notification you need to use above API with HTTP or XMPP protocols. We will use HTTP protocol in examples below. There are two variants of FCM implementation:

  • With FCM App Server
  • Without FCM App Server

Let take a look on both of them and try to understand what variant is better and why. Before that I will explain some definitions that I will use later.

FCM connection servers - provided by Google. These servers take messages from an app server and send them to a client app running on a device. Google provides connection servers for HTTP and XMPP [From official documentation].

FCM app server - server that you must implement in your environment. This app server sends data to a client app via the chosen FCM connection server, using the appropriate XMPP or HTTP protocol [From official documentation].

User A, B - mobile clients that are authenticated with Firebase Auth.

Firebase Database - cloud storage provided by Firebase for using by authenticated users.

To post FCM message through Firebase Connection Server with, for example, HTTP protocol, you need to pass Authorization Header with every POST request. Authorization header is your FCM API Key, that you can retrieve from Firebase Console. What does it mean? It means that it’s possible to send the FCM messages from a mobile device that would be sent from a dedicated FCM App Server. However, this has the following problems.

  • You're putting your API key in your app, so somebody could decompile your APK to get it.
  • Your users would need some way to share their GCM registration IDs with each other. If two users had both their IDs expire at the same time, there would be no way to share them again.
  • Someone can use Man-In-The-Middle attack to catch your FCM API key while sending.

That’s why i STRONGLY recommend you to use your own FCM App server, that will be responsible for interaction with FCM Connection Server. Building your app and hosting it on AppEngine or other alternative cloud server would take about an hour to write, and cost less than $10 a month, even for a ton of users.

In this article i will show you how to implement FCM user2user messages for both variants.

The variant with FCM App server. Let understand how it works

User B logins into application and subscribes to specific, specially dedicated for this user topic: "user_USER_ID". User A writes a new NotificationRequest record into "notification_requests" child of Firebase Database, that contains "message", "user_id" - id of User B, and other optional additional info. On the same time, we have Firebase App Server, that tracks / monitors appearance of new records in "notification_requests" child. If new record appears, FCM app server will build and send new POST HTTP request to FCM Connection Server using FCM API key as "Authorization" header, and remove the record from database child after successful sending. After it, FCM Connection Server will send Push Notification to User B, and Voila… user B will see new Push Notification in Status Bar.

Below I will show you an example of described above Firebase App Server. I took the code from this article:

https://firebase.googleblog.com/2016/08/sending-notifications-between-android.html

and modified it a bit. It is a JS code, that should be run on Node.js server.

var firebase = require('firebase');  
var request = require('request');

var API_KEY = "FCM_API_KEY";

firebase.initializeApp({  
  serviceAccount: "account.json",
  databaseURL: "FIREBASE_DATABASE_URL"
});
ref = firebase.database().ref();

function listenForNotificationRequests() {  
  var requests = ref.child('notificationRequests');
  requests.on('child_added', function(requestSnapshot) {
    var request = requestSnapshot.val();
    sendNotificationToUser(
      request.user_id,
      request.message,
      function() {
        requestSnapshot.ref.remove();
      }
    );
  }, function(error) {
    console.error(error);
  });
};

function sendNotificationToUser(userId, message, onSuccess) {  
  request({
    url: 'https://fcm.googleapis.com/fcm/send',
    method: 'POST',
    headers: {
      'Content-Type': ' application/json',
      'Authorization': 'key=' + API_KEY
    },
    body: JSON.stringify({
      notification: {
        title: message
      },
      to: '/topics/user_' + userId
    })
  }, function(error, response, body) {
    if (response.statusCode >= 400) {
      console.error('HTTP Error: ' + response.statusCode + ' - ' + response.statusMessage);
    } else {
      onSuccess();
    }
  });
}

// start listening
listenForNotificationRequests();  

There is no auto-retry in above simple script, so it will only retry failed notifications when you restart the script. For a more scalable approach, consider using our firebase-queue library.

To make the script above work, install firebase and request Npm modules. Also, replace API_KEY variable with your FCM API Key that can be found in Firebase Console. Also provide your databaseURL instead of example text. And the last thing you need - to provide account.json file in the same directory where you place your server code file. Follow the instruction on official documentation that helps you to get needed serviceAccount file.

https://firebase.google.com/docs/admin/setup

After you successfully configured server code, just run it on your Node.js server.

Then implement writing new NotificationRequest to Firebase Database child "notification_requests" on mobile client A. This part is out of scope. Refer to Firebase samples if you don’t know how to configure your Database and how to write to it.

https://firebase.google.com/docs/samples/

For now, you would have configured and implemented Firebase App Server, that monitors "notification_requests" child in Firebase Database, and Android Client, that can send a message (write it to "notification_requests" child in Firebase Database), and receive FCM push notification. And that’s all. Everything should work now.

The variant without FCM App Server

Warning! I have already mentioned that this way is not good because of its security. You will do it on your own risk. Remember, that it is better to use Firebase App Server, that will be responsible for interaction with FCM Connection Server.

How does the variant without FCM app server work?

The idea is to use FCM Connection Server API directly from the client, using HTTP POST requests with Authorization Header, that is your FCM API Key.
User B should be subscribed to "user_USER_B_ID".

You should subscribe for the channel in two places:

  • Application onCreate;
  • After successful Sign In.
FirebaseAuth auth;  
String currentUser = auth.getCurrentUser() ;  
if (currentUser !=  null) {  
FirebaseMessaging.getInstance().subscribeToTopic("user_" + currentUser);  
}

Also you should unsubscribe from topic in two places:

  • Application onTerminate
  • After successful Sign Out
FirebaseAuth auth;  
String currentUser = auth.getCurrentUser() ;  
if (currentUser !=  null) {  
FirebaseMessaging.getInstance().unsubscribeFromTopic("user_" + currentUser);  
}

Also your application should be properly configured to receive FCM notifications. I posted a link how to do it at the beginning of this article.

Now lets write the method that will send your notification on User A device:

   private void sendNotification(String text, String senderId, String receiverId) {
        //send Push Notification
        HttpsURLConnection connection = null;
        try {

            URL url = new URL("https://fcm.googleapis.com/fcm/send");
            connection = (HttpsURLConnection) url.openConnection();
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type", "application/json");
            //Put below you FCM API Key instead
            connection.setRequestProperty("Authorization", "key="
                    + “YOUR_FCM_API_KEY”);

            JSONObject root = new JSONObject();
            JSONObject data = new JSONObject();
            data.put(KEY_FCM_TEXT, text);
            data.put(KEY_FCM_SENDER_ID, sender);
            root.put("data", data);
            root.put("to", "/topics/user_" + receiverId);

            byte[] outputBytes = root.toString().getBytes("UTF-8");
            OutputStream os = connection.getOutputStream();
            os.write(outputBytes);
            os.flush();
            os.close();
            connection.getInputStream(); //do not remove this line. request will not work without it gg

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) connection.disconnect();
        }
    }

Use this method in place where you would like to send a notification. Pay attention that we don’t use “notification” key for building the request and a bit later I will explain why. We only use "data" block with custom keys.

The last step is to override public void onMessageReceived(RemoteMessage remoteMessage) if we want to customize our received by User B Push Notifications.

Next you should know is very important. So, the official Firebase documentation says:

For downstream messaging, FCM provides two types of payload: notification and data. For notification type, FCM automatically displays the message to end-user devices on behalf of the client app. Notifications have a predefined set of user-visible keys. For data type, client app is responsible for processing data messages. Data messages have only custom key-value pairs.

It means, practically, that if you use "notification" payload in your request, you won’t be able to customize your notification appearing and behavior. For example, status bar notification will only appear if the application is in background. In all other cases, it will not appear.
But what if we want to show our notification in all cases when user is not on appropriate screen. For example, user B is reading news in your app (app is in foreground), and in that time user A send a message to User B. With default "notification" payload behavior it’s not possible. User B does not see Push Notification. But, it is possible without "notification" payload, using only "data" payload with custom Key-Value pairs. That’s why in code for notification sending I used only "data" payload.

The full code for implementation for custom notifications:

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {  
    Map<String, String> data = remoteMessage.getData();
    if (data == null) {
    return;
    }
    String senderId = data.get(FirebaseMessageEntityStore.KEY_FCM_SENDER_ID);
if (TextUtils.isEmpty(senderId)) {  
        return;
    }
String senderName = getUsernameById(senderId);  
showNotification(senderId, senderName, message, true);  
}


private void showNotifcation(String senderId, String senderFullName, String message, boolean needSound) {  
        if (DialogPresenter.getCurrentPeerId() != null) {
            return;
        }
        Intent intent = new Intent(this, DialogActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intent.putExtra(DialogActivity.KEY_PEER_ID, senderId);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT);

        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
                .setContentTitle(senderFullName == null ? getString(com.buddysearch.android.library.R.string.loading) : senderFullName)
                .setContentText(message)
                .setSmallIcon(com.buddysearch.android.data.R.drawable.ic_notification_message)
                .setAutoCancel(true)
                .setContentIntent(pendingIntent);
        if (needSound) {
            notificationBuilder.setSound(defaultSoundUri);
        }

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(senderId.hashCode(), notificationBuilder.build());
    }

Now you can test how does sending and receiving FCM-based Push Notifications without FCM App Server work.

And finally, at the beginning of the article I promised you to provide an example of FCM in real messenger-application. Here it is:

https://github.com/ihorvitruk/buddysearch

Compile and run it on your environment and see how FCM works in practice.

Feel free to ask everything you want to know about it in Issues section or by emailing me at ihorvitruk@gmail.com

If you liked this article, share it with friends!

Contact us