0
0
Android Kotlinmobile~20 mins

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

Choose your learning style9 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>'.