In this post I will show you how I implemented Firebase Cloud Messaging in Android Compose to receive push notifications.
Firebase Cloud Messaging is useful to push notifications to a specific user or group of users. My previous post https://shift2dev.com/push-notifications-with-firebase-cloud-messaging-from-php-server/ shows how to implement the server side functionality to your project. This post will be referencing the client side implementation in Android Compose.
First you will need to create a project in your Firebase Console. Run through the setup wizard. You will need to download the google-services.json config file to include in your app. This file holds your Firebase Cloud Messaging API information to authenticate with Google.
Below I added Datastore to project to capture and retain the FCM device token. You can exclude if you want. It is not necessary to implement with Firebase Cloud Messaging but could be useful. In my case, I added this as a local way to identify if the app needs to request the token from startup and when the new token is dispatched. My project also ultimately stores the FCM tokens in a database. The tokens are sent/update in the database at the same times the TokenManager is updated.
Setup Reference:
https://firebase.google.com/docs/cloud-messaging/android/first-message
Configuration
You will need to add the google-services.json file to the root of your project. To do this, change the project view scope from Android to Project in the left pane.

Paste the file in the app directory
Next edit project level build.gradle and add the following
alias(libs.plugins.gms.google.services) apply false
Edit the app level build.gradle and add the following
plugins {
...
alias(libs.plugins.gms.google.services)
}
...
dependencies {
...
//firebase
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics)
implementation(libs.firebase.messaging.ktx)
//datastore preferences
implementation(libs.androidx.datastore.preferences)
...
Edit the libs.versions.toml
[versions]
...
gmsGoogleServices = "4.4.2"
firebaseBom = "33.8.0"
datastorePreferences = "1.1.2"
[libraries]
...
firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
firebase-messaging-ktx = { module = "com.google.firebase:firebase-messaging-ktx" }
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
...
[plugins]
...
gms-google-services = { id = "com.google.gms.google-services", version.ref = "gmsGoogleServices" }
Edit the AndroidManifest.xml. We need to add permissions, the messaging service and some meta information for Firebase Cloud Messaging.
...
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />
...
<service
android:name=".service.MyMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@mipmap/ic_launcher" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/dark_orange" />
</application>
I have created a TokenManager. This will save the token for quick access if the app needs it later. As stated before, this is not necessary but I am including in case your project needs this functionality.
class TokenSingleton {
companion object {
lateinit var dataStore: DataStore<Preferences>
@Volatile
private var INSTANCE: TokenSingleton? = null
fun getInstance(context : Context): TokenSingleton? {
if (INSTANCE == null) {
synchronized(this) {
if (INSTANCE == null) {
// create the singleton instance
INSTANCE = TokenSingleton()
dataStore = PreferenceDataStoreFactory.create(
corruptionHandler = ReplaceFileCorruptionHandler(
produceNewData = { emptyPreferences() },
),
produceFile = {
context
.preferencesDataStoreFile("test_fcm_datastore")
}
)
}
}
}
return INSTANCE
}
}
suspend fun getFCMToken() : String {
val prefs = dataStore.data.first()
return prefs[PrefKeys.FCM_TOKEN] ?: PrefKeys.EMPTY
}
suspend fun updateFCMToken(fcm_token : String){
dataStore.edit {
it[PrefKeys.FCM_TOKEN] = fcm_token
}
}
suspend fun clearData(){
dataStore.edit { it.clear() }
}
}
private object PrefKeys {
val FCM_TOKEN = stringPreferencesKey("fcm_token")
val EMPTY = "HEADER.PAYLOAD.SIGNATURE"
}
In ViewModel, add the following functions
public fun checkToken(event: UserIntent.updateCurrentUserData) {
viewModelScope.launch {
val fcm_token = runBlocking {
TokenSingleton.getInstance(applicationContext)?.getFCMToken()
}
if(fcm_token == null || fcm_token.equals("HEADER.PAYLOAD.SIGNATURE")){
getFCMToken()
}
}
}
private fun getFCMToken(event: UserIntent.getFCMToken){
viewModelScope.launch {
//val token = Firebase.messaging.token.await()
Firebase.messaging.token.addOnCompleteListener { task ->
if(!task.isSuccessful){
Log.d("FCM token", "Failed to generate token")
return@addOnCompleteListener
}
val token = task.result
Log.d("FCM token:", token)
runBlocking {
TokenSingleton.getInstance(applicationContext)?.updateFCMToken(token)
}
}
}
}
Create MyMessagingService – this is what creates the notification as well as updates the FCM token if a new token is dispatched
class MyMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
super.onNewToken(token)
Log.d("FCM token:", token)
runBlocking {
TokenSingleton.getInstance(applicationContext)?.updateFCMToken(token)
}
}
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
message.notification?.let { msg ->
createNotification(msg)
}
}
private fun createNotification(message: RemoteMessage.Notification) {
MyNotificationBuilder.create(
this@MyMessagingService,
applicationContext.resources.getInteger(R.integer.fcm_notification),
getString(R.string.default_notification_channel_id),
message.title.toString(),
message.body.toString(),
"FCM Test",
"FCM Test Description",
NotificationManager.IMPORTANCE_DEFAULT
)
}
override fun onDeletedMessages() {
super.onDeletedMessages()
}
}
Create MyNotificationBuilder – this does exactly what the name implies, builds the notification
object MyNotificationBuilder {
fun create(
context: Context,
notifyID : Int,
channelId: String,
title: String,
content: String,
channelName: String,
channelDesc: String,
importance: Int
) {
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationManager.createNotificationChannel(
createNotificationChannel(
channelId,
channelName,
channelDesc,
importance
)
)
}
val pendingIntent: PendingIntent =
PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
val builder = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle(title)
.setContentText(content)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
notificationManager.notify(notifyID, builder.build())
}
@SuppressLint("NewApi")
private fun createNotificationChannel(
channelId: String,
channelName: String,
channelDesc: String,
importance: Int
): NotificationChannel {
val channel = NotificationChannel(channelId, channelName, importance).apply {
description = channelDesc
}
return channel
}
}
Now you will just need to find a good place to call the ViewModel function to get a FCM token. I added a DisposableEffect that calls the function in onCreate
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(key1 = lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_CREATE) {
viewModel.checkToken()
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
Test
To test the notification, you will need to go to your Firebase Console and create a new messaging campaign. The link at start of post will walk you through this process.
Hope this was useful.