In the "Notifying your users with FCM" blog post, we shared guidance on using FCM in modern Android with regard to all the power management features. Following on that, let's look at the common workflow of FCM messages -- notification messages and data messages, and how to handle these messages in your code.

Notification messages

When sending push notifications to your users, notification messages are the preferred method if you want to take advantage of the Firebase Console and let the Android system handle notification posting. Notification messages are high priority by default, and high priority FCM messages will still be delivered to users immediately even when the device is idle.

When using an FCM notification message, the system handles showing the notification on behalf of your app when it's in the background. When your app is in the foreground, the FCM notification message is delivered to the onMessageReceived()handler and you can handle it by posting a notification if needed or update the app content with the FCM payload data (Max 4KB) or fetch content from app server.

Notification workflow

An App server sends a notification message (or a notification message with a data payload) to a user:

1. App server sends a notification message*

Notification messages are high priority by default, and collapsible--the following message will replace the current message if it's not delivered yet.

2. The FCM message is delivered to the client app

When your app is in Foreground. Developer needs to handle the notification message in the handler. Post a notification or update app content in the FCM callback. When your app is in Background. Notification delivered to System Tray.

3. Use getIntent()

If there's a notification posted, your user may tap the notification and open your app. You can then use the data in getIntent() to update the app content if needed.

Note: if the user dismisses the notification, data will not be delivered when app opens.

Code sample

class SimpleFirebaseMessagingService : FirebaseMessagingService() {

   private val TAG = "spFirebaseMsgService"

   override fun onMessageReceived(remoteMessage: RemoteMessage) {

       // when App is in foreground, notification message:
       if (remoteMessage.notification != null) {
           // post notification if needed
           updateContent(remoteMessage.notification)
       }

       // process data payload
   }

   private fun updateContent(notification: RemoteMessage.Notification) {}

} 

Data message

You should use data messages if you need to handle the notification in the client app, whether to customize the notification or to decrypt the received payload data.

A data message is normal priority by default, which means it will be batched to the next maintenance window when the device is in Doze, by default.

Handling Messages

When sending data messages, you need to handle the messages in the onMessageReceived() callback in the client app. The suggested approach in the handler is as following:

  1. When you need to notify the user, post a notification first.
  2. Update app content with payload data.
  3. If needed, schedule the work to fetch additional data from app server.

Messages workflow

An App server sends data messages to notify users with end-to-end encrypted FCM message:

1. App server sends a data message to FCM server

The data message should include all the data needed for a notification in the payload

2. Handle the message and post a notification

When the message is received in the FCM app client, you have a short window to handle the message and post a notification.

  • Handle payload decryption and customized notification in the onMessageReceived() callback, then immediately post a notification to the user
  • Note that FCM does NOT guarantee the order of delivery. Developers should handle it when messages are delivered out of order; when you send non-collapsible messages, you can keep track of the order with a message sequence ID, and hold off notifying the user until all messages are received or you give up waiting in the event the prior messages TTL expired.
  • Use the payload data to update the client content and store data locally
  • If you need additional data that exceeds the 4KB FCM payload limit, schedule a job or use WorkManager to fetch the data but don't block displaying an initial notification on this.
  • WorkManager is recommended when it's stable
  • If you need to update the notification with data fetched, update the notification if it's still active
  • User opens the app
  • If app content is not up-to-date, check if work scheduled in 2.3 is completed, and update your app content accordingly.

Note: Keep in mind that the FCM handler only has approximately 20 seconds once the message is received, as it's designed to complete short tasks like posting a notification. If you need to process longer than this window, we recommend scheduling a job or use the WorkManager API.

Code sample

class SimpleFirebaseMessagingService : FirebaseMessagingService() {

   private val TAG = "spFirebaseMsgService"

   override fun onMessageReceived(remoteMessage: RemoteMessage) {

       // Use data payload to create a notification
       if (remoteMessage.data.isNotEmpty()) {

           // step 2.1: decrypt payload
           val notificationMessage = decryptPayload(remoteMessage.data)
           // step 2.1: display notification immediately
           sendNotification(notificationMessage)
           // step 2.2: update app content with payload data
           updateContent(remoteMessage.data)

           // Optional step 2.3: if needed, fetch data from app server
           /* if additional data is needed or payload is bigger than 4KB, App server can send a flag to notify client*/
           if (remoteMessage.data["url"] != null) {
               scheduleJob()
               // use WorkManager when it's stable
           }
       }
       // process notification payload when app in foreground...
   }

   private fun decryptPayload(dataPayload: Map<String, String>): String {
       return "decrypted message"
   }

   private fun sendNotification(notificationMessage: String) {}
   private fun updateContent(dataPayload: Map<String, String>) {}
   private fun scheduleWork() {
       // it's recommended to use WorkManager when it's stable, use JobScheduler
       // on background work complete, update the notification if still active
   }
} 

FCM on modern Android

Android has introduced many power improvement features in recent versions, so make sure you review and test your FCM use cases against these features. You can learn more about Android Power features and how it works with FCM from this blog post.

If you are not using FCM yet, it's time to upgrade. The C2DM library was officially deprecated in 2012 and shut down completely in 2015, making it no longer compatible with modern Android. We also announced in April 2018 that Google Cloud Messaging (GCM) server and client APIs have been deprecated and will be removed as soon as April 11th, 2019. You can find out more about the announcement and migration guides in this blog post.

To take advantage of all the new features and functionality that FCM and Android provide, we recommend using FCM today. To get started, visit the Firebase Cloud Messaging documentation.