Bird
Raised Fist0
Android Kotlinmobile~20 mins

Cloud Messaging (push notifications) in Android Kotlin - Mini App: Build & Ship

Choose your learning style10 modes available
Build: PushNotificationDemo
This screen registers the app for Firebase Cloud Messaging and shows a simple message when a push notification is received while the app is open.
Target UI
-------------------------
| Push Notification Demo |
-------------------------
|                       |
|  Last message:         |
|  <no message yet>      |
|                       |
-------------------------
Register the app with Firebase Cloud Messaging (FCM) to receive push notifications.
Display the last received push notification message on the screen.
Handle push notifications when the app is in the foreground.
Request notification permission if needed (for Android 13+).
Starter Code
Android Kotlin
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
                }
            }
        }
    }
}
Task 1
Task 2
Task 3
Task 4
Solution
Android Kotlin
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.

Final Result
Completed Screen
-------------------------
| Push Notification Demo |
-------------------------
|                       |
|  Last message:         |
|  Hello from FCM!       |
|                       |
-------------------------
When a push notification arrives while the app is open, the 'Last message' text updates to show the notification content.
If the app is launched fresh, it requests notification permission on Android 13+.
Notifications appear in the system tray if the app is in background.
Stretch Goal
Add a button to clear the last received message from the screen.
💡 Hint
Add a Button composable below the message text that sets the message state back to '<no message yet>'.

Practice

(1/5)
1. What is the main purpose of Firebase Cloud Messaging (FCM) in Android apps?
easy
A. To manage app permissions and settings
B. To store user data locally on the device
C. To send push notifications to users even when the app is closed
D. To create user interfaces with buttons and text

Solution

  1. Step 1: Understand FCM's role

    Firebase Cloud Messaging is designed to send messages or notifications to users' devices.
  2. Step 2: Identify the correct purpose

    It works even if the app is closed, keeping users updated with push notifications.
  3. Final Answer:

    To send push notifications to users even when the app is closed -> Option C
  4. Quick Check:

    FCM purpose = push notifications [OK]
Hint: Remember: FCM = push notifications, not UI or storage [OK]
Common Mistakes:
  • Confusing FCM with local data storage
  • Thinking FCM manages UI elements
  • Assuming FCM controls app permissions
2. Which Kotlin class should you extend to handle incoming Firebase push messages in your Android app?
easy
A. FirebaseMessagingService
B. FirebaseDatabase
C. FirebaseAuth
D. FirebaseStorage

Solution

  1. Step 1: Identify the service for message handling

    FirebaseMessagingService is the class designed to receive and handle push notifications.
  2. Step 2: Exclude unrelated Firebase classes

    FirebaseDatabase, FirebaseAuth, and FirebaseStorage serve different purposes like data storage, authentication, and file storage.
  3. Final Answer:

    FirebaseMessagingService -> Option A
  4. Quick Check:

    Message handler class = FirebaseMessagingService [OK]
Hint: Use FirebaseMessagingService to receive messages [OK]
Common Mistakes:
  • Choosing FirebaseDatabase for message handling
  • Confusing FirebaseAuth with messaging
  • Using FirebaseStorage instead of messaging service
3. Given the following Kotlin code snippet inside a FirebaseMessagingService subclass, what will be logged when a message is received?
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")
}
medium
A. Title: No Title, Body: No Body if notification is null
B. App crashes if notification is null
C. Title and Body are always empty strings
D. Logs only the body, not the title

Solution

  1. Step 1: Analyze null safety with Elvis operator

    The code uses ?: to provide default strings if notification or its fields are null.
  2. 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.
  3. Final Answer:

    Title: No Title, Body: No Body if notification is null -> Option A
  4. Quick Check:

    Elvis operator handles nulls = default text logged [OK]
Hint: Elvis operator ?: gives default when null [OK]
Common Mistakes:
  • Assuming app crashes on null notification
  • Thinking title/body become empty strings
  • Believing only body is logged
4. You wrote this code to get the device token for FCM but it never logs the token. What is the error?
FirebaseMessaging.getInstance().token.addOnCompleteListener {
    if (!it.isSuccessful) return@addOnCompleteListener
    val token = it.result
    Log.d("FCM", "Token: $token")
}
medium
A. You need to call addOnSuccessListener instead of addOnCompleteListener
B. You forgot to add internet permission in AndroidManifest.xml
C. You must call getInstance() with context parameter
D. You should use getToken() instead of token property

Solution

  1. Step 1: Check the API for token retrieval

    FirebaseMessaging.getInstance().getToken() is the correct method; there is no .token property.
  2. 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.
  3. Final Answer:

    You should use getToken() instead of token property -> Option D
  4. Quick Check:

    No token property = use getToken() [OK]
Hint: Use getToken() method to fetch FCM token [OK]
Common Mistakes:
  • Forgetting internet permission
  • Thinking context is required for getInstance()
  • Using wrong listener type for token retrieval
5. You want to send a push notification only to devices that have a specific user preference enabled. Which step is essential to implement this targeting correctly?
hard
A. Use FirebaseDatabase to store notifications and query them on the device
B. Send the device token to your server and filter tokens based on user preferences
C. Send notifications to all devices and let the app ignore unwanted ones
D. Use FirebaseAuth to authenticate users before sending notifications

Solution

  1. Step 1: Understand device token role

    Device tokens uniquely identify devices to receive push notifications.
  2. Step 2: Implement server-side filtering

    Send tokens to your server and filter them by user preferences before sending notifications.
  3. Final Answer:

    Send the device token to your server and filter tokens based on user preferences -> Option B
  4. Quick Check:

    Token + server filtering = targeted notifications [OK]
Hint: Always send tokens to server for targeted notifications [OK]
Common Mistakes:
  • Sending notifications to all devices without filtering
  • Using FirebaseDatabase instead of server filtering
  • Confusing authentication with notification targeting
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard