------------------------- | Push Notification Demo | ------------------------- | | | Last message: | | <no message yet> | | | -------------------------
Cloud Messaging (push notifications) in Android Kotlin - Mini App: Build & Ship
package com.example.pushdemo import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { var message by remember { mutableStateOf("<no message yet>") } Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { Column(modifier = Modifier.padding(16.dp)) { Text(text = "Push Notification Demo", style = MaterialTheme.typography.headlineMedium) Text(text = "Last message:") Text(text = message, modifier = Modifier.padding(top = 8.dp)) // TODO: Add code to register for FCM and update 'message' when notification arrives } } } } }
package com.example.pushdemo import android.Manifest import android.app.NotificationChannel import android.app.NotificationManager import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage class MainActivity : ComponentActivity() { private var messageState = mutableStateOf("<no message yet>") private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> if (isGranted) { subscribeToTopic() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) createNotificationChannel() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { when { ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED -> { subscribeToTopic() } else -> { requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) } } } else { subscribeToTopic() } setContent { val message by messageState Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { Column(modifier = Modifier.padding(16.dp)) { Text(text = "Push Notification Demo", style = MaterialTheme.typography.headlineMedium) Text(text = "Last message:") Text(text = message, modifier = Modifier.padding(top = 8.dp)) } } } } private fun subscribeToTopic() { FirebaseMessaging.getInstance().token.addOnCompleteListener { task -> if (task.isSuccessful) { val token = task.result // Normally send token to your server } } } private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( "default_channel", "Default Channel", NotificationManager.IMPORTANCE_DEFAULT ) val manager = getSystemService(NotificationManager::class.java) manager.createNotificationChannel(channel) } } fun updateMessage(newMessage: String) { messageState.value = newMessage } } class MyFirebaseMessagingService : FirebaseMessagingService() { override fun onMessageReceived(remoteMessage: RemoteMessage) { val messageBody = remoteMessage.notification?.body ?: remoteMessage.data["message"] ?: "No message content" // Since this is a service, update UI via a broadcast or other method // For simplicity, we use a static reference (not recommended for production) (applicationContext as? MainActivity)?.updateMessage(messageBody) } }
This solution sets up Firebase Cloud Messaging in an Android app using Kotlin and Jetpack Compose.
We request notification permission on Android 13+ devices. If granted, we subscribe to FCM token to receive messages.
A notification channel is created for Android 8+ to show notifications properly.
The UI shows the last received message using Compose state.
We implement MyFirebaseMessagingService to receive messages. When a message arrives, it updates the UI state with the message content.
Note: In a real app, updating UI from a service requires a more robust approach like broadcasts or a shared ViewModel. Here, a simplified static cast is shown for learning.
------------------------- | Push Notification Demo | ------------------------- | | | Last message: | | Hello from FCM! | | | -------------------------
Practice
Solution
Step 1: Understand FCM's role
Firebase Cloud Messaging is designed to send messages or notifications to users' devices.Step 2: Identify the correct purpose
It works even if the app is closed, keeping users updated with push notifications.Final Answer:
To send push notifications to users even when the app is closed -> Option CQuick Check:
FCM purpose = push notifications [OK]
- Confusing FCM with local data storage
- Thinking FCM manages UI elements
- Assuming FCM controls app permissions
Solution
Step 1: Identify the service for message handling
FirebaseMessagingService is the class designed to receive and handle push notifications.Step 2: Exclude unrelated Firebase classes
FirebaseDatabase, FirebaseAuth, and FirebaseStorage serve different purposes like data storage, authentication, and file storage.Final Answer:
FirebaseMessagingService -> Option AQuick Check:
Message handler class = FirebaseMessagingService [OK]
- Choosing FirebaseDatabase for message handling
- Confusing FirebaseAuth with messaging
- Using FirebaseStorage instead of messaging service
override fun onMessageReceived(remoteMessage: RemoteMessage) {
val title = remoteMessage.notification?.title ?: "No Title"
val body = remoteMessage.notification?.body ?: "No Body"
Log.d("FCM", "Title: $title, Body: $body")
}Solution
Step 1: Analyze null safety with Elvis operator
The code uses ?: to provide default strings if notification or its fields are null.Step 2: Understand logging behavior
If notification is null, title and body become "No Title" and "No Body" respectively, so the log shows these defaults.Final Answer:
Title: No Title, Body: No Body if notification is null -> Option AQuick Check:
Elvis operator handles nulls = default text logged [OK]
- Assuming app crashes on null notification
- Thinking title/body become empty strings
- Believing only body is logged
FirebaseMessaging.getInstance().token.addOnCompleteListener {
if (!it.isSuccessful) return@addOnCompleteListener
val token = it.result
Log.d("FCM", "Token: $token")
}Solution
Step 1: Check the API for token retrieval
FirebaseMessaging.getInstance().getToken() is the correct method; there is no .token property.Step 2: Verify other options
Internet permission is needed to run, but the code won't compile due to undefined token property. getInstance() without context works, and addOnCompleteListener is correct.Final Answer:
You should use getToken() instead of token property -> Option DQuick Check:
No token property = use getToken() [OK]
- Forgetting internet permission
- Thinking context is required for getInstance()
- Using wrong listener type for token retrieval
Solution
Step 1: Understand device token role
Device tokens uniquely identify devices to receive push notifications.Step 2: Implement server-side filtering
Send tokens to your server and filter them by user preferences before sending notifications.Final Answer:
Send the device token to your server and filter tokens based on user preferences -> Option BQuick Check:
Token + server filtering = targeted notifications [OK]
- Sending notifications to all devices without filtering
- Using FirebaseDatabase instead of server filtering
- Confusing authentication with notification targeting
Ready to prove it?
Pass the test and this pattern moves unknown
