In this post I will show you how I implemented FCM ( Firebase Cloud Messaging ) in Android Compose to receive push notifications.
Resource
Firebase Cloud Messaging is useful to push notifications to a specific user or group of users. My previous post shows how to implement the server side functionality to your project.
Push Notifications With Firebase Cloud Messaging From PHP
This post will be referencing the client side implementation in Android Compose.
Setup
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
build.gradle
Next edit project level build.gradle and add the following
alias(libs.plugins.gms.google.services) apply false
In 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)
...
libs.versions.toml
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" }
AndroidManifest.xml
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>
Code
TokenManager
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"
}
ViewModel
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)
}
}
}
}
MyMessagingService
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()
}
}
MyNotificationBuilder
MyNotificationBuilder 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
}
}
Activity
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.