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.