Best Practice to add FCM Notification in your app

Ankit
Bobble Engineering
Published in
7 min readNov 24, 2022
Notification Message on Android Device

To send notifications in your app we use something called the FCM (Firebase Cloud Messaging) service of Firebase. If in case you don’t know what is Firebase, Google Firebase is a Google-backed application development software that enables developers to develop iOS, Android and Web apps. Firebase provides tools for tracking analytics, reporting and fixing app crashes, creating marketing and product experiment and for notifications we use Firebase Cloud Messaging (FCM) which is a cross-platform messaging tool that lets companies reliably receive and deliver messages on iOS, Android and the web at no cost.

In order to add a notifications service we have to understand some conditions because it depends on certain factors like whether the app is in the background or foreground, what will be the content of the notification message whether we want to send some extra data messages with our notification.

FCM Architecture

Let's see different scenarios, First of all, we can compose or build our notification message either from the Firebase console or our app server then it goes to FCM Backend then to the android transport layer and then to SDK on the device. There are three kinds of messages we can send,1-> Only notification which contains Title, Body and image 2-> Notification + Data Payload which contains a notification message with some extra key-value pair data 3-> Only data in which we don't want any notification but some data which can be only sent by our server, FCM console can’t send only data messages.

Sending only notification messages: When our app is in the background it will be handled by Firebase automatically we don’t need to write a single line of code we just have to integrate firebase with our project. But when our app will be in the foreground we have to handle it by making a service otherwise we don’t see any notification we sent.

Sending notification message with extra data payload: See why we want to send extra data with the notification we want to use it in our app. We also want that when we touch our notification it will lead to our app or some other activity of our app we want. So to do this we use “Pending Intent”. So when our app is in the background and we receive a notification, FCM automatically handles notification and its pending intent too. So by default when we click the notification message it will send to your main activity and we have to handle the extra message which we received with notification in the main activity. But when our app is in the foreground we have to handle notifications, its pending intent and extra data received.

Now follow the steps to do all things we discussed above, Go to Android Studio and create a new project with an empty activity. In the top right corner, there is an icon to sign in to your google account, sign in first in order to use cloud functionality in your app. After sign in ,it will show a SUCCESS message in the browser get back to android studio. Now go to Tools -> Firebase -> Cloud Messaging -> Set up Firebase Cloud Messaging[Kotlin] Now follow the steps shown there, Click to Connect to Firebase, you will be redirected to firebase in the browser, click on add project and create a project there and add Connect. Hurray, your app is connected to firebase now follow the second step Add FCM to your app and accept changes.

Now go to firebase console “console.firebase.google.com”, go to your project and select cloud messaging and create a new campaign and choose notification messages. Add all the details like [notification title, notification body, image url], in the schedule section set it to “now” and then publish. Put your app in the background and you will get a notification. The main task is to configure notifications when our app is in the foreground lets do this now.

Whenever some device installs our app Firebase generates a “Token” for it using which Firebase identifies the different devices which help in the categorization of users. It is necessary because we can’t send create account notifications to our existing user , or we want to send some specific notifications to some premium users, so for these scenarios, these tokens are required.

Notification Channel: If App is in the foreground we need a notification channel to be created to get notifications in the foreground on all devices above version OREO.FCM auto creates a channel if the version is above OREO but we need it to create as if we have to show notification in the foreground. So to do so create a ChannelCreation.kt file and add the following code to create a channel

class ChannelCreation: Application() {
val channelId = "NotificationChannel"
private val channelName = "NotificationChannel"
private val channelDesc = "Creating Notification Channel for versions above Oreo"

override fun onCreate() {
super.onCreate()

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val channel = NotificationChannel(channelId,channelName, NotificationManager.IMPORTANCE_HIGH)
channel.description = channelDesc
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
}

To receive a notification when the app is in the foreground we have to make a service so create a ServiceClass.kt file and add the following code.


// Data messages and foreground app notifications need this class to work in foreground
// We override FirebaseMessagingService methods to perform actions
class ServiceClass : FirebaseMessagingService() {

private val tAG = "tagged"
val myMessage = "myMessage"

// Whenever a message is received this part call for foreground
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)

Log.d(tAG, "onMessageReceived Called")
Log.d(tAG, "onMessageReceived: Message is received from: " + message.from)

// This notification is from remote message to work in foreground
if (message.notification != null) {
val title = message.notification!!.title
val body = message.notification!!.body

// Creating pending intent to go to our main activity when it clicked
val intentIs = Intent(this, MainActivity::class.java)
val pi: PendingIntent = PendingIntent.getActivity(
this, 0, intentIs, PendingIntent.FLAG_MUTABLE)


//as we have to show notification in foreground we have to create notification manually
val notificationBuilder = NotificationCompat.Builder(this, ChannelCreation().channelId)
.setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle(title)
.setColor(Color.BLUE)
.setContentText(body)
.setContentIntent(pi)
.setAutoCancel(true)
.build()

val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(1002, notificationBuilder)

}

//if only data is there in remote message
if (message.data.isNotEmpty()) {
Log.d(tAG, "onMessageReceived: Data: " + message.data.toString())
}
}

//
override fun onDeletedMessages() {
super.onDeletedMessages()
Log.d(tAG, "onDeletedMessages Called")
}

// By any cause if a new Token is generated this function will be call
// to update token on our server (Monitoring)
override fun onNewToken(token: String) {
super.onNewToken(token)

Log.d(tAG, "onNewToken Called")
}
}

Modify your Manifest file like this

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application
android:name=".ChannelCreation"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AFirebaseCloudMessagingApp"
tools:targetApi="31">
<activity
android:name=".MainActivity2"
android:exported="false">
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>

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

</manifest>

and now when you send a notification it will be configured when your app is in foreground state. Here we are done with only notifications but what to do if we want some key value pair data with our notification message . To do this again go to firebase console and start a new campaign and do everything same but in additional option add the custom data in key and value format and then publish the notification messages. When you recieve notification and you click on it , it will lead you to your main activity when app is in background state but you will not see the data messages. To see those messages add this code to your main activity (MainActivity.kt) which actually is listening an intent.


class MainActivity : AppCompatActivity() {
private lateinit var keyValueTextView:TextView
private val tAG = "tagged"

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

keyValueTextView = findViewById(R.id.keyValue)

// Here we are listening an intent, initiated due to notification click
// which by default create a pending intent to launcher activity(app is in background)
if( intent != null && intent.hasExtra("from"))
{
keyValueTextView.text = ""
for(key in intent.extras!!.keySet()) {
Log.d(tAG, "Key: " + key + "Data: " + intent.extras!!.getString(key))
keyValueTextView.append(key+" ----> "+intent.extras!!.getString(key)+"\n")
}
}

// As Firebase added in application an Instance Id is "autogenerated" by Firebase(Token) which is unique for every devices
// Token get Refreshed, If wipe app data,uninstall app and reinstall,app restore on new device so we have to Monitor it
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (task.isSuccessful) {
val token = task.result
Log.d(tAG, "Unique Token for the device is $token")
}
else {
return@OnCompleteListener
}
})
}
}

and change your activity_main.xml like this

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:layout_margin="12dp">

<TextView
android:id="@+id/keyValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="KEYS ------> VALUES"
android:textAlignment="center"
android:textStyle="bold"
android:layout_marginTop="5dp"/>

</LinearLayout>

Now again send a new notification with data payload and when your app is in the background and you receive the notification when you click it, It will lead you to the main activity and you will be able to see all the data you added with your notification message. If your app is in the foreground the data messages can be seen in logcat as it is handled by us in onmessagerecieved in the service class.

Note 👉 When you send notification messages from Firebase console it takes time to get recieved on the android device so wait for it to come.

And here we come to the end of our tutorial and I hope this will help you to add a notification feature to your app, just follow all the steps and codes are commented on to understand what is happening. Thank You !!

--

--