Firebase Cloud Messaging in Android : Efficiently handling the Device Token

Firebase Cloud Messaging in Android : Efficiently handling the Device Token

FCM workflow Overview:

  1. A unique identifier of every device is generated each time the fresh(First launch, clear data, re-installing, etc) app is there. We’ll call it Device FCM Token or just Token.

  2. Token is required to send PUSH notifications to those particular devices.(We can also group some Tokens and send group notification)

  3. You may want to identify the verified user using the UID that is generated after the user is being logged in.

  4. So, there are 2 things to identify a user. UID and FCM Token. But how and why are they different?

  5. A UID is a unique ID for verified users and there can be scenerios where you only send notifications to verified users. Ex: After purchasing a Subscription plan, a PUSH notification will be sent from your server. So, your server will check that…ok this UID bought the subscription and that UID is present in (1,2,3…as many) devices so get all the Device FCM Tokens linked with UID and send the notification to all.

  6. So, that means every time a UID is generated(after login), you have to send a request to your server to link the UID with the Device FCM Token(which was already auto-generated).

  7. If you want to also send the notifications to un-verified users(without login), you can still follow the same practices mentioned in this article.

Pre-requisites(MUST):

  1. You should already have the Firebase integrated with your Application.

  2. Enabled FCM from your Firebase project Console.

  3. You should be clear about your notification generator/token receiver. i.e. The person or place from where notifications are pushed/sent. In my case it was our own AWS server. So, that should be an HTTP call for the app to send the FCM token to server. or you can also store it in FireStore.

  4. Generating the PUSH notification is not your headache. So, app will not start any Intent to send any Notifications. It should be ALWAYS from the backend(according to sets of conditions defined).

  5. You should handle your app specific data sent through notification and react accordingly.(Not shown in this article as it will vary for all).

Implementation:

  1. Create MyFirebaseMessagingService class and override required methods:
class MyFirebaseMessagingService : FirebaseMessagingService() {

    //Called when message is received and app is in foreground.
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        // Check if message contains a data payload.
        remoteMessage.data.let {
            Log.d(TAG, "Message data payload: " + remoteMessage.data)
        }

        // Check if message contains a notification payload.
        remoteMessage.notification?.let {
            Log.d(TAG, "Message Notification Body: ${it.body}")
            sendNotification(it.title!!, it.body!!, it.icon, it.imageUrl)
        }
    }

    override fun onNewToken(token: String) {
        Log.d(TAG, "Refreshed token: $token")
        //Send this token to server
    }
}
  • You can access the newly generated token from this onNewToken method.

  • But, also you can access the token any time using “Firebase.messaging.token.await()”

  • So, we will be accessing the token only using the second way.

  • The simple and easiest way is to make a call to our server in the onCreate() and send the token.

  • But, every time making the call will be useless when the token has been already sent.

  • How can we make sure we will send the token to our server only when it’s newly generated and not sent yet?

  • One way you might thing is to use this onNewToken method and make the request from here. As this method will be only triggered when token is newly generated.

  • But HOW? I can’t find any working solution with this approach, but there is a better solution.

  • Use DATASTORE.

  • We will store a BOOLEAN value whether our token is already uploaded or not, and if not only that time we’ll hit the server.

  • So, in this service class set the ‘isFcmTokenUploaded’ boolean value as false.

  • And we’ll do it by calling the method from our Repository.

@AndroidEntryPoint//Make sure to add this
class MyFirebaseMessagingService : FirebaseMessagingService() {

    @Inject
    lateinit var repo: TokenRepo//Inject the repo and call your method to update the bool

    override fun onNewToken(token: String) {
        Log.d(TAG, "Refreshed token: $token")
        repo.setNewTokenAvailable(token)
    }
}

Checkout this article to learn about DATASTORE.

If using module — I would suggest you to create this service class in your main app module as we’ll need to start the intent to MainActivity after notification click.

2. Add this service class to your AndroidManifest. This will help us get the notification when app is in backgroud.

<service
    android:name=".MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

Optionally add the notification meta-data

<meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/ic_notifications" />
<meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/colorAccent" />

3. Now, we know that our datastore have a bool value ‘isFCMTokenUploaded’ as false. So, that means after having the UID(after successful login), we can link the token with UID by making the request to the server.

If you want to also send the token even without having the UID still there is no issue, do it whenever you want. Just make a check…

if(!isFCMTokenUploaded){ //Upload the token and set this bool to true }

4. Now, after having a successful login upload the token and only after that update your Ui state.

viewModelScope.launch {//In Login method(inside viewmodel)
    authRepo.signInWithCredential(authCredential).collect {//Implement your own ways
        if (it is ResponseState.Success && !isFcmTokenUploaded.value) {//must check for successful login
            val token = Firebase.messaging.token.await()//get the token
            uploadFcmToken(token)//Call upload token from repo and wait
        }
        _loginState.value = it.toUiState()//after uploading update the Ui State
    }
}


private suspend fun uploadFcmToken(tokenDTO: TokenDTO) {
    viewModelScope.launch {
        authRepo.uploadFcmToken(tokenDTO)
    }.join()
}

Make sure to mark the ‘isFcmTokenUploaded’ as true after uploading the token successfully.

That’s it!!.

Now, you have setup an automatic flow, whenever a new token is generated the datastore will be updated and after login(old or new UID) you will check the datastore and always push the latest token with UID.

That way you are avoiding making the unnecessary calls to the server.

Leave a comment if you are facing any issues or have any improvements.

I hope you found this helpful. If yes, then do FOLLOW ‘Sagar Malhotra’ for more Android-related content.