Set up a Firebase Cloud Messaging client app on Android

Pandiyan Mani
5 min readApr 9, 2023

First we need to add google-services required for Firebase Integration on the project level build.gradle file

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.4.2' apply false
id 'com.android.library' version '7.4.2' apply false
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
id "com.google.gms.google-services" version "4.3.14" apply false // newly added
}

And in app level build.gradle add

plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.gms.google-services' // newly added
}

dependencies {

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

implementation 'com.google.firebase:firebase-messaging-ktx:23.0.2'
implementation platform('com.google.firebase:firebase-bom:31.4.0')
implementation 'com.google.firebase:firebase-analytics-ktx'
}

A service that extends FirebaseMessagingService. This is required if you want to do any message handling beyond receiving notifications on apps in the background. To receive notifications in foregrounded apps, to receive data payload, to send upstream messages, and so on, you must extend this service.

package com.app.pushnotification

import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class MyFirebaseMessagingService: FirebaseMessagingService() {

override fun onNewToken(token: String) {
Log.d("New_Token", token)
}

override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
Log.d("onMessageReceived", "onMessageReceived: ${remoteMessage.from}")
}
}
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

Android 13 introduces a new runtime permission for showing notifications. This affects all apps running on Android 13 or higher that use FCM notifications.

By default, the FCM SDK (version 23.0.6 or higher) includes the POST_NOTIFICATIONS permission defined in the manifest. However, your app will also need to request the runtime version of this permission via the constant, android.permission.POST_NOTIFICATIONS. Your app will not be allowed to show notifications until the user has granted this permission.

// Declare the launcher at the top of your Activity/Fragment:
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// FCM SDK (and your app) can post notifications.
} else {
// TODO: Inform user that that your app will not show notifications.
}
}

private fun askNotificationPermission() {
// This is only necessary for API level >= 33 (TIRAMISU)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==
PackageManager.PERMISSION_GRANTED
) {
// FCM SDK (and your app) can post notifications.
} else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
// TODO: display an educational UI explaining to the user the features that will be enabled
// by them granting the POST_NOTIFICATION permission. This UI should provide the user
// "OK" and "No thanks" buttons. If the user selects "OK," directly request the permission.
// If the user selects "No thanks," allow the user to continue without notifications.
} else {
// Directly ask for the permission
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}

Now lets implement notification code to display what message we displayed from the FCM. So you can see inside MyFirebaseMessagingService class on onMessageReceived() function we added the notification code where we display the title and body from FCM.

override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
Log.d("onMessageReceived", "onMessageReceived: ${remoteMessage.notification!!.body}")

// it is a class to notify the user of events that happen.
// This is how you tell the user that something has happened in the
// background.
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

// pendingIntent is an intent for future use i.e after
// the notification is clicked, this intent will come into action
val intent = Intent(this, MainActivity::class.java)

// FLAG_UPDATE_CURRENT specifies that if a previous
// PendingIntent already exists, then the current one
// will update it with the latest intent
// 0 is the request code, using it later with the
// same method again will get back the same pending
// intent for future reference
// intent passed here is to our afterNotification class
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)


// checking if android version is greater than oreo(API 26) or not
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationChannel = NotificationChannel(channelId, description, NotificationManager.IMPORTANCE_HIGH)
notificationChannel.enableLights(true)
notificationChannel.lightColor = Color.GREEN
notificationChannel.enableVibration(false)
notificationManager.createNotificationChannel(notificationChannel)

builder = Notification.Builder(this, channelId)
.setContentTitle(remoteMessage.notification!!.title)
.setContentText(remoteMessage.notification!!.body)
.setSmallIcon(R.drawable.ic_launcher_background)
.setLargeIcon(BitmapFactory.decodeResource(this.resources, R.drawable.ic_launcher_background))
.setContentIntent(pendingIntent)
} else {

builder = Notification.Builder(this)
.setContentTitle(remoteMessage.notification!!.title)
.setContentText(remoteMessage.notification!!.body)
.setSmallIcon(R.drawable.ic_launcher_background)
.setLargeIcon(BitmapFactory.decodeResource(this.resources, R.drawable.ic_launcher_background))
.setContentIntent(pendingIntent)
}
notificationManager.notify(1234, builder.build())

}

Lets see the entire Code MyFirebaseMessagingService.kt

package com.app.pushnotification

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.Color
import android.os.Build
import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class MyFirebaseMessagingService: FirebaseMessagingService() {

lateinit var notificationManager: NotificationManager
lateinit var notificationChannel: NotificationChannel
lateinit var builder: Notification.Builder
private val channelId = "i.apps.notifications"
private val description = "Test notification"

override fun onNewToken(token: String) {
Log.d("New_Token", token)
}

override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
Log.d("onMessageReceived", "onMessageReceived: ${remoteMessage.notification!!.body}")

// it is a class to notify the user of events that happen.
// This is how you tell the user that something has happened in the
// background.
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

// pendingIntent is an intent for future use i.e after
// the notification is clicked, this intent will come into action
val intent = Intent(this, MainActivity::class.java)

// FLAG_UPDATE_CURRENT specifies that if a previous
// PendingIntent already exists, then the current one
// will update it with the latest intent
// 0 is the request code, using it later with the
// same method again will get back the same pending
// intent for future reference
// intent passed here is to our afterNotification class
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)


// checking if android version is greater than oreo(API 26) or not
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationChannel = NotificationChannel(channelId, description, NotificationManager.IMPORTANCE_HIGH)
notificationChannel.enableLights(true)
notificationChannel.lightColor = Color.GREEN
notificationChannel.enableVibration(false)
notificationManager.createNotificationChannel(notificationChannel)

builder = Notification.Builder(this, channelId)
.setContentTitle(remoteMessage.notification!!.title)
.setContentText(remoteMessage.notification!!.body)
.setSmallIcon(R.drawable.ic_launcher_background)
.setLargeIcon(BitmapFactory.decodeResource(this.resources, R.drawable.ic_launcher_background))
.setContentIntent(pendingIntent)
} else {

builder = Notification.Builder(this)
.setContentTitle(remoteMessage.notification!!.title)
.setContentText(remoteMessage.notification!!.body)
.setSmallIcon(R.drawable.ic_launcher_background)
.setLargeIcon(BitmapFactory.decodeResource(this.resources, R.drawable.ic_launcher_background))
.setContentIntent(pendingIntent)
}
notificationManager.notify(1234, builder.build())

}
}

Lets see the entire Code MainActivity.kt

package com.app.pushnotification

import android.content.pm.PackageManager
import android.os.Build
import android.Manifest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat

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

askNotificationPermission()
}


// Declare the launcher at the top of your Activity/Fragment:
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// FCM SDK (and your app) can post notifications.
} else {
// TODO: Inform user that that your app will not show notifications.
}
}

private fun askNotificationPermission() {
// This is only necessary for API level >= 33 (TIRAMISU)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==
PackageManager.PERMISSION_GRANTED
) {
// FCM SDK (and your app) can post notifications.
} else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
// TODO: display an educational UI explaining to the user the features that will be enabled
// by them granting the POST_NOTIFICATION permission. This UI should provide the user
// "OK" and "No thanks" buttons. If the user selects "OK," directly request the permission.
// If the user selects "No thanks," allow the user to continue without notifications.
} else {
// Directly ask for the permission
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
}

Final Output:

--

--