Spotted a bug? Have a great idea? Help us make google.dev great!

This codelab is part of the Advanced Android in Kotlin course. You'll get the most value out of this course if you work through the codelabs in sequence, but it is not mandatory. All the course codelabs are listed on the Advanced Android in Kotlin codelabs landing page.

Introduction

In the previous codelab, you added notifications to your egg timer which are created and triggered within your app. Another important use case of notifications is to remotely send push notifications which can be received even when your app is not running.

Push notifications are a great way to let users know about an update or remind them of a task or feature. Imagine waiting for a product to be in stock again. With a push notification, a shopping app can let you know about stock updates rather than you having to check the stock status everyday.

Just like using notifications, make sure you respect your users with push notifications. If the notification content is not interesting or timely for the user, they can easily turn off all notifications from your app.

Firebase Cloud Messaging is part of the Firebase platform for mobile development. Usually you would need to set up a server from scratch that can communicate with mobile devices to trigger notifications. With Firebase Cloud Messaging, you can send notifications to all or a subset of your installed app users without setting up a server. For example, you can notify of a reminder, a special promotion, or a free gift.You can remotely push a notification to a single device or multiple devices. These so-called push notifications can also carry a data payload from the server to the mobile device.

What is a push notification?

Push notifications are notifications that the server "pushes" to mobile devices. They can be delivered to a device regardless of whether your app is running or not.

Publish/subscribe allows backend apps to push relevant content to interested clients. Without a publish/subscribe model, a client would need to periodically check for updates from a backend. Apart from being tedious for users, and unreliable, as the number of clients grows, these periodic checks would result in strain on networking and processing resources both on the client and the server side.

You can also use Firebase Cloud Messages to transfer data from your backend app or from a Firebase project to your users.

In this codelab, you will learn how to use Firebase Cloud Messaging to send push notifications for your Android app, as well as send data.

What you should already know

You should be familiar with:

  • How to create Android apps in Kotlin. In particular, work with the Android SDK.
  • How to architect apps using the Architecture Components and data binding.
  • A basic understanding of BroadcastReceivers
  • A basic understanding of AlarmManager
  • How to create and send notifications using NotificationManager.

What you'll learn

  • How to push messages to the user via Firebase Cloud Messaging.
  • How to send data from a backend to your app using FCM Data Messages.

What you'll do

  • Add push notifications to the starter app.
  • Handle FCM while your app is running.
  • Transfer data with FCM.

In this codelab you will be working on the code from the previous Using Notifications in Android Apps codelab. In the previous codelab, you built an egg timer app that sends notifications when the cooking timer is up. In this codelab, you will add Firebase Cloud Messaging to send push notifications to your app users to remind them to eat eggs.

To get the sample app, you can either:

Clone the repository from GitHub and switch to the starter branch:

$  git clone https://github.com/googlecodelabs/android-kotlin-notifications-fcm


Alternatively, you can download the repository as a Zip file, unzip it, and open it in Android Studio.

Download Zip

Step 1: Create a Firebase project

Before you can add Firebase to your Android app, you need to create a Firebase project to connect to your Android app.

  1. Log in to the Firebase console.
  2. Click Add project, then select or enter a Project name. Name your project fcm-codelab.
  3. Click Continue.
  4. You can skip setting up Google Analytics and choose the Not Right Now option.
  5. Click Create Project to finish setting up the Firebase project.

Step 2: Register your app with Firebase

Now that you have a Firebase project, you can add your Android app to it.

  1. In the center of the Firebase console's project overview page, click the Android icon to launch the setup workflow.

  1. Enter your app's application ID in the Android package name field. For your project enter the package id com.example.android.eggtimernotifications, as the Firebase application ID.
  2. Click Register app.

Important: Make sure you enter the correct ID for your app because you cannot add or modify this value after you've registered your app with your Firebase project.

Step 3: Add the Firebase configuration file to your project

Add the Firebase Android configuration file to your app.

  1. Click Download google-services.json to obtain your Firebase Android config file (google-services.json). Make sure the config file is not appended with additional characters and should only be named google-services.json
  2. Move your config file into the module (app-level) directory of your app.

Step 4: Configure your Android project to enable Firebase products

To enable Firebase products in your app, you must add the google-services plugin to your Gradle files.

  1. In your root-level (project-level) Gradle file (build.gradle), check that you have Google's Maven repository.
  2. Then, add rules to include the Google Services plugin.

build.gradle

buildscript {

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
  }

  dependencies {
    // ...

    // Add the following line:
    classpath 'com.google.gms:google-services:4.3.0'  // Google Services plugin
  }
}

allprojects {
  // ...

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
    // ...
  }
}
  1. In your module (app-level) Gradle file (usually app/build.gradle), add a line to apply the plugin to the bottom of the file.

app/build.gradle

apply plugin: 'com.android.application'

android {
  // ...
}

// Add the following line to the bottom of the file:
apply plugin: 'com.google.gms.google-services'  // Google Play services Gradle plugin

In this task, you will add Firebase Cloud Messaging (FCM) to your project to make use of push notifications.

The Android service code for FCM of this codelab is given in MyFirebaseMessagingService.kt. In the following steps you will add code to your Android app.

You will use the Notification composer to test your implementation. Notification composer is a tool that helps you to compose and send messages from the Firebase console website.

  1. Open MyFirebaseMessagingService.kt
  2. Inspect the file and in particular the following functions.
  • onNewToken()—Called automatically if your service is registered in the Android manifest. This function is called when you first run your app and every time Firebase issues a new token for your app. A token is an access key to your Firebase backend project. It's generated for your specific client device. With this token, Firebase knows which client the backend is talking to. Firebase also knows if this client is valid and has access to this Firebase project.
  • onMessageReceived— Called when your app is running and Firebase sends a message to your app. This function receives a RemoteMessage object which can carry a notification or data message payload. You will learn more about the differences of notification and data message payloads later in this codelab.

Step 1: Sending FCM notifications to a single device

The Notifications console lets you test sending a notification. To send a message to a specific device using the console, you need to know that device's registration token.

On initial startup of your app, the Firebase backend generates a registration token for the client app instance. This token is generated when you first run your app, and Firebase may recreate this token as the user continues using the app. Whether the token is newly created or refreshed, the onNewToken() function will be called with the new token. If you want to target a single device or create a group of devices to which you want to send a broadcast message, you'll need to access this token by extending FirebaseMessagingService and overriding onNewToken().

  1. Open AndroidManifest.xml and uncomment the following code to enable the MyFirebaseMessagingService for the egg timer app. The service meta-data in the Android manifest registers MyFirebaseMessagingService as a service and adds an intent filter so this service will receive messages sent from FCM. The last part of the metadata declares breakfast_notification_channel_id as default_notification_channel_id for Firebase. You will be using this ID in the next step.
<!-- AndroidManifest.xml -->
<!-- TODO: Step 3.0 uncomment to start the service  -->

        <service
                android:name=".MyFirebaseMessagingService"
                android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>
        <!-- [START fcm_default_icon] -->
        <!--
 Set custom default icon. This is used when no icon is set for incoming notification messages.
             See README(https://goo.gl/l4GJaQ) for more.
        -->
        <meta-data
                android:name="com.google.firebase.messaging.default_notification_icon"
                android:resource="@drawable/common_google_signin_btn_icon_dark"/>
        <!--
 Set color used with incoming notification messages. This is used when no color is set for the incoming
             notification message. See README(https://goo.gl/6BKBk7) for more.
        -->
        <meta-data
                android:name="com.google.firebase.messaging.default_notification_color"
                android:resource="@color/colorAccent"/> <!-- [END fcm_default_icon] -->
        <!-- [START fcm_default_channel] -->
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_channel_id"
            android:value="@string/breakfast_notification_channel_id" />
        <!-- [END fcm_default_channel] -->

It is a good idea to create a new notification channel for the FCM since your users may want to enable/disable egg timer or FCM push notifications separately.

  1. Open EggTimerFragment.kt and in onCreateView() add the following below the default channel creation code.
// EggTimerFragment.kt

   // TODO: Step 3.1 create a new channel for FCM
    createChannel(
        getString(R.string.breakfast_notification_channel_id),
        getString(R.string.breakfast_notification_channel_name)
    )
  1. Open MyFirebaseMessagingService.kt and uncomment the onNewToken() function. This function will be called when a new token is generated.
// MyFirebaseMessagingService.kt

   // TODO: Step 3.2 log registration token
    // [START on_new_token]
    /**
     * Called if InstanceID token is updated. This may occur if the security of
     * the previous token had been compromised. Note that this is called when the     
     * InstanceID token is initially generated so this is where you would retrieve     
     * the token.
     */
    override fun onNewToken(token: String?) {
        Log.d(TAG, "Refreshed token: $token")

        // If you want to send messages to this application instance or
        // manage this apps subscriptions on the server side, send the
        // Instance ID token to your app server.
        sendRegistrationToServer(token)
    }
    // [END on_new_token]
  1. Run the egg timer app.
  2. Observe logcat (View > Tool Windows > Logcat). You should see a log line showing your token similar to the one below. This is the token you need in order to send a message to this device. This function is only called when a new token is created.
2019-07-23 13:09:15.243 2312-2459/com.example.android.eggtimernotifications D/MyFirebaseMsgService: Refreshed token: f2esflBoQbI:APA91bFMzNNFaIskjr6KIV4zKjnPA4hxekmrtbrtba2aDbh593WQnm11ed54Mv6MZ9Yeerver7pzgwfKx7R9BHFffLBItLEgPvrtF0TtX9ToCrXZ5y7Hd-m

Note: If you do not see the log with the token, your app may already have received the token before. In that case, uninstalling the app will help you to receive a new token.

Now you can test by sending a notification. In order to send a notification, you will use the Notification composer.

  1. Open the Firebase Console and select your project.
  2. Next, select Cloud Messaging from the navigation on the left.
  3. Click Send your first message.

  1. Enter the notification title and text, and select Send test message.

  1. Copy your app token from logcat.

  1. Add and select the token to the popup window in Firebase Console.

  1. On your device, put the Egg Timer app in the background.
  2. In the Firebase Console Test on Device popup, click Test.
  1. After you click Test, the targeted client device (with the app in the background) should receive the notification in the system notifications tray. (You will see more on how to handle the FCM messages when your app is in the foreground later.)

Task: Sending FCM Notifications to a Topic

FCM topic messaging is based on the publish/subscribe model.

A messaging app can be a good example for the Publish/Subscribe model. Imagine the app checking for new messages every 10 seconds to see if there are any new messages. This will not only drain your phone battery, but will also use unnecessary network resources, and will create an unnecessary load on the server. Instead, the app can subscribe and be notified when there are new messages.

Topics allow you to send a message to multiple devices that have opted in to that particular topic. For clients, topics are a specific data sources which the client is interested in. For the server, topics are groups of devices which have opted in to receive updates on a specific data source. Topics can be used for purposes like subscribing to news, weather forecasts, or sports results. For this part of the codelab, you will create a "breakfast" topic to remind the interested app users to eat eggs with their breakfast.

To subscribe to a topic, the client app calls the Firebase Cloud Messaging subscribeToTopic() function with the FCM topic name, "breakfast". This call can have two outcomes. If the caller succeeds, the OnCompleteListener callback will be called with the subscribed message. If the client fails to subscribe, the same callback will receive an error message.

In your app, you will automatically subscribe your users to the breakfast topic, although in most cases, you may want leave the option of which topics to subscribe to to the user.

  1. Open EggTimerFragment.kt and find the empty subscribeTopic() function.
  2. Get an instance of FirebaseMessaging and call the subscibeToTopic() function with the topic name.
  3. Add an addOnCompleteListener to get notified back from FCM on whether your subscription succeeded or failed.
// EggTimerFragment.kt

   // TODO: Step 3.3 subscribe to breakfast topic
    private fun subscribeTopic() {
        // [START subscribe_topics]
        FirebaseMessaging.getInstance().subscribeToTopic(TOPIC)
            .addOnCompleteListener { task ->
                var msg = getString(R.string.message_subscribed)
                if (!task.isSuccessful) {
                    msg = getString(R.string.message_subscribe_failed)
                }
                Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
            }
        // [END subscribe_topics]
    }
  1. Call the subscribeTopic() function to subscribe to a topic when the app starts. Scroll up to onCreateView() and add a call to subscribeTopic().
// EggTimerFragment.kt

   // TODO: Step 3.4 call subscribe topics on start
    subscribeTopic()

    return binding.root
  1. To subscribe to the breakfast topic, run the app again. You should see a toast message saying "Subscribed to topic".

Now you can test sending messages to a topic.

  1. Open the Notifications composerand select New Notification.
  2. Set the notification Title and Text as before.
  3. This time, instead of sending the message to a single device, click Topic under Target and enter breakfast as the topic name.

  1. Select Now for Scheduling.

  1. Make sure your app is running in the background on your testing device.
  1. Next click Review and click Publish. If you can run the app on more than one device, you can test and observe the notification is received on all devices subscribed to this topic.

The app has more than one channel for notifications now, Egg and Breakfast.

  1. Long-click the app icon, select Info, and click Notifications. you should see the notification Egg and Breakfast channels shown in the screenshot below. If you deselect the Breakfast channel, your app will not receive any notifications sent over this channel.

When using notifications, always keep in mind that users can turn off any notification channel at any time.

Step 1: Data messages

FCM messages can also contain data payload. When you need to display FCM messages as notifications, you need to use notification messages. If you want to pass data to your app and process the messages in the client app, you need to use data messages.

In order to handle FCM messages, you need to handle the data payload in the onMessageReceived() function of MyFirebaseMessagingService. The data payload is stored in the data property of the remoteMessage object. Both, the remoteMessage object and the data property can be null.

  1. Open MyFirebaseMessagingService.
  2. Check if the data property of the remoteMessage object has some value and print the data to the log.
// MyFirebaseMessagingService.kt

    // [START receive_message]
    override fun onMessageReceived(remoteMessage: RemoteMessage?) {
        // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
        Log.d(TAG, "From: ${remoteMessage?.from}")
        
       // TODO: Step 3.5 check messages for data
        // Check if message contains a data payload.
        remoteMessage?.data?.let {
            Log.d(TAG, "Message data payload: " + remoteMessage.data)
        }

    }
    // [END receive_message]

To test your code, you can use the Notifications composer again.

  1. Open the Notification composer, create a new message and select 'breakfast' topic.
  2. This time, when you get to Step 4, Additional options, set the Custom data key-value pairs as follows: use eggs for the key and 3 as the value.

  1. Make sure your app is running and in the foreground.
  2. Send the message and observe data message log in the logcat.

If your app is in the background, the FCM message will trigger an automatic notification and the onMessageReceived() function will receive the remoteMessage object only when the user clicks the notification.

Step 2: Handling messages in the foreground and background

App behavior when receiving messages that include both notification and data payloads depends on whether the app is in the background or the foreground—essentially, whether or not it is active at the time of receipt.

  • When in the background, if the message has a notification payload, the notification is automatically shown in the notification tray. If the message also has a data payload, the data payload will be handled by the app when the user taps on the notification.
  • When in the foreground, if the message notification has payloads, the notification will not appear automatically. The app needs to decide how to handle the notification in the onMessageReceived() function. If the message also has data payload, both payloads will be handled by the app.

For the purpose of this codelab, you want to remind the app user to have some eggs for breakfast. You are not planning to send any data, but you also want to make sure the reminder notification shows up whether the app is in foreground or background.

When you send an FCM message to devices on which the egg timer app is installed, the notification message will be shown automatically if the app is not running (or in the background). However, if the app is running and in the foreground, then the notification is not shown automatically, and instead, the app code decides what to do with the message. If the app is in the foreground when it receives an FCM message, the onMessageReceived() function will be triggered automatically with the FCM message. This is where your app can silently handle notification and data payloads or trigger a notification.

For your app, you want to make sure the user gets the reminder when the app is in the foreground, so let's implement some code to trigger a notification.

  1. Open the onMessageReceived() function in MyFirebaseMessagingService again.
  2. Right after the code you recently added to check the data message, add the following code which sends a notification using the notifications framework.
// MyFirebaseMessagingService.kt

    // TODO: Step 3.6 check messages for notification and call sendNotification
    // Check if message contains a notification payload.
    remoteMessage?.notification?.let {
        Log.d(TAG, "Message Notification Body: ${it.body}")
        sendNotification(it.body!!)
    }
  1. If you run the app again and send a notification by using the Notifications composer, you should see a notification just like you used to see in the first part of the codelab regardless of whether the app is in the foreground or background.

The solution code is in the master branch of your downloaded code.

Udacity course:

Android developer documentation:

Firebase documentation:

For links to other codelabs in this course, see the Advanced Android in Kotlin codelabs landing page.