diff --git a/messaging/app/build.gradle b/messaging/app/build.gradle
index 9324342ec4..efe533230b 100644
--- a/messaging/app/build.gradle
+++ b/messaging/app/build.gradle
@@ -8,12 +8,12 @@ check.dependsOn 'assembleDebugAndroidTest'
android {
namespace 'com.google.firebase.quickstart.fcm'
- compileSdk 33
+ compileSdk 34
defaultConfig {
applicationId "com.google.firebase.quickstart.fcm"
minSdk 21 // minSdk would be 19 without compose
- targetSdk 33
+ targetSdk 34
versionCode 1
versionName "1.0"
multiDexEnabled true
@@ -97,6 +97,8 @@ dependencies {
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.activity:activity-compose:1.5.1'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
+
// Testing dependencies
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
androidTestImplementation 'androidx.test:runner:1.5.1'
diff --git a/messaging/app/src/main/AndroidManifest.xml b/messaging/app/src/main/AndroidManifest.xml
index 7ccf60a017..0b8f6a73c2 100644
--- a/messaging/app/src/main/AndroidManifest.xml
+++ b/messaging/app/src/main/AndroidManifest.xml
@@ -36,6 +36,7 @@
+
{
return kotlin.collections.listOf(
- Choice(
- "Java",
- "Run the Firebase Cloud Messaging quickstart written in Java.",
- Intent(this, MainActivity::class.java)),
- Choice(
- "Kotlin",
- "Run the Firebase Cloud Messaging written in Kotlin.",
- Intent(this, com.google.firebase.quickstart.fcm.kotlin.MainActivity::class.java))
+ Choice(
+ "Java",
+ "Run the Firebase Cloud Messaging quickstart written in Java.",
+ Intent(this, MainActivity::class.java)),
+ Choice(
+ "Kotlin",
+ "Run the Firebase Cloud Messaging written in Kotlin.",
+ Intent(this, com.google.firebase.quickstart.fcm.kotlin.MainActivity::class.java)),
+ Choice(
+ "Compose",
+ "Run the Firebase Cloud Messaging written in Compose.",
+ Intent(this, com.google.firebase.quickstart.fcm.kotlin.ComposeMainActivity::class.java))
)
}
}
\ No newline at end of file
diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ComposeMainActivity.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ComposeMainActivity.kt
new file mode 100644
index 0000000000..0a938f3568
--- /dev/null
+++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ComposeMainActivity.kt
@@ -0,0 +1,246 @@
+package com.google.firebase.quickstart.fcm.kotlin
+
+import android.app.Activity
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Build
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.compose.setContent
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.SnackbarHost
+import androidx.compose.material.SnackbarHostState
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.google.firebase.quickstart.fcm.R
+import com.google.firebase.quickstart.fcm.kotlin.data.SubscriptionState
+import com.google.firebase.quickstart.fcm.kotlin.ui.theme.FirebaseMessagingTheme
+import kotlinx.coroutines.launch
+
+class ComposeMainActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ FirebaseMessagingTheme {
+ // A surface container using the 'background' color from the theme
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colors.background
+ ) {
+ MainScreen()
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun MainScreen(
+ lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+ fcmViewModel: FirebaseMessagingViewModel = viewModel(factory = FirebaseMessagingViewModel.Factory)
+) {
+ val context = LocalContext.current
+ val scope = rememberCoroutineScope()
+ val snackbarHostState = remember { SnackbarHostState() }
+
+ val activity = context.findActivity()
+ val intent = activity?.intent
+
+ val requestPermissionLauncher = rememberLauncherForActivityResult(
+ ActivityResultContracts.RequestPermission()
+ ) { isGranted ->
+ var message = context.getString(R.string.msg_permission_granted)
+ if (!isGranted) {
+ message = context.getString(R.string.msg_permission_failed)
+ }
+
+ scope.launch {
+ snackbarHostState.showSnackbar(message)
+ }
+ }
+
+ DisposableEffect(lifecycleOwner) {
+ val observer = LifecycleEventObserver { _, event ->
+ if (event == Lifecycle.Event.ON_CREATE) {
+ //Create Notification Channel
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ fcmViewModel.setNotificationChannel(
+ context,
+ context.getString(R.string.default_notification_channel_id),
+ context.getString(R.string.default_notification_channel_name)
+ )
+ }
+
+ intent?.let { intentData ->
+ fcmViewModel.logNotificationData(intentData)
+ }
+
+ scope.launch {
+ snackbarHostState.showSnackbar(context.getString(R.string.msg_setup_readme_instructions))
+ }
+
+ fcmViewModel.askNotificationPermission(context, requestPermissionLauncher)
+ }
+ }
+
+ lifecycleOwner.lifecycle.addObserver(observer)
+
+ onDispose {
+ lifecycleOwner.lifecycle.removeObserver(observer)
+ }
+ }
+
+ LaunchedEffect(fcmViewModel.token) {
+ fcmViewModel.token.collect {
+ if(it.isNotEmpty()) {
+ snackbarHostState.showSnackbar(context.getString(R.string.msg_token_fmt, it))
+ }
+ }
+ }
+
+ LaunchedEffect(fcmViewModel.subscriptionState) {
+ fcmViewModel.subscriptionState.collect { state ->
+ when (state) {
+ SubscriptionState.Success -> { snackbarHostState.showSnackbar(context.getString(R.string.msg_subscribed)) }
+ SubscriptionState.Failed -> { snackbarHostState.showSnackbar(context.getString(R.string.msg_subscribe_failed)) }
+ SubscriptionState.Loading -> { }
+ }
+ }
+ }
+
+ Scaffold(
+ snackbarHost = {
+ SnackbarHost(hostState = snackbarHostState)
+ },
+ modifier = Modifier
+ .fillMaxSize(),
+ topBar = {
+ TopAppBar(
+ backgroundColor = colorResource(R.color.colorPrimary)
+ ) {
+ Text(
+ text = stringResource(R.string.app_name),
+ style = MaterialTheme.typography.h6,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(8.dp),
+ color = Color.White
+ )
+ }
+ },
+ content = { innerPadding ->
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding)
+ ) {
+ MainContent(fcmViewModel)
+ }
+ }
+ )
+}
+
+@Composable
+fun MainContent(
+ fcmViewModel: FirebaseMessagingViewModel
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp),
+ verticalArrangement = Arrangement.Top,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(R.drawable.firebase_lockup_400),
+ contentDescription = "Firebase logo",
+ modifier = Modifier.fillMaxWidth(),
+ )
+ Text(
+ text = stringResource(R.string.quickstart_message),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(top = 16.dp, start=8.dp, end=8.dp, bottom=16.dp)
+ )
+ Button(
+ modifier = Modifier
+ .width(200.dp)
+ .wrapContentHeight()
+ .padding(0.dp, 20.dp, 0.dp, 0.dp),
+ colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(R.color.colorPrimary)),
+ onClick = {
+ fcmViewModel.subscribeToTopic("weather")
+ }
+ ) {
+ Text(
+ textAlign = TextAlign.Center,
+ text = stringResource(R.string.subscribe_to_weather).uppercase(),
+ color = Color.White,
+ )
+ }
+ Button(
+ modifier = Modifier
+ .width(200.dp)
+ .wrapContentHeight()
+ .padding(0.dp, 20.dp, 0.dp, 0.dp),
+ colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(R.color.colorPrimary)),
+ onClick = {
+ fcmViewModel.getToken()
+ }
+ ) {
+ Text(
+ text = stringResource(R.string.log_token).uppercase(),
+ color = Color.White,
+ )
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun MainAppViewPreview() {
+ FirebaseMessagingTheme {
+ MainScreen()
+ }
+}
+
+fun Context.findActivity(): Activity? = when (this) {
+ is Activity -> this
+ is ContextWrapper -> baseContext.findActivity()
+ else -> null
+}
\ No newline at end of file
diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/FirebaseMessagingViewModel.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/FirebaseMessagingViewModel.kt
new file mode 100644
index 0000000000..2e632719b0
--- /dev/null
+++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/FirebaseMessagingViewModel.kt
@@ -0,0 +1,109 @@
+package com.google.firebase.quickstart.fcm.kotlin
+
+import android.Manifest
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.util.Log
+import androidx.activity.result.ActivityResultLauncher
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.CreationExtras
+import com.google.firebase.messaging.FirebaseMessaging
+import com.google.firebase.quickstart.fcm.kotlin.data.SubscriptionState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.tasks.await
+
+class FirebaseMessagingViewModel(
+ private val messaging: FirebaseMessaging
+): ViewModel() {
+
+ private var _token: MutableStateFlow = MutableStateFlow("")
+ val token: MutableStateFlow = _token
+
+ private var _subscriptionState = MutableStateFlow(SubscriptionState.Loading)
+ val subscriptionState: StateFlow = _subscriptionState
+
+ fun askNotificationPermission(
+ context: Context,
+ requestPermissionLauncher: ActivityResultLauncher
+ ) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) ==
+ PackageManager.PERMISSION_DENIED
+ ) {
+ requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
+ }
+ }
+ }
+
+ fun setNotificationChannel(context: Context, channelId: String, channelName: String ) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ // Create channel to show notifications.
+ context.getSystemService(NotificationManager::class.java)?.createNotificationChannel(
+ NotificationChannel(
+ channelId,
+ channelName,
+ NotificationManager.IMPORTANCE_LOW,
+ ),
+ )
+ }
+ }
+
+ fun logNotificationData(intent: Intent) {
+ intent.extras?.let {
+ it.keySet().forEach { key ->
+ val value = intent.extras?.getString(key)
+ Log.d(TAG, "Key: $key Value: $value")
+ }
+ }
+ }
+
+ fun getToken() {
+ viewModelScope.launch {
+ try {
+ _token.value = messaging.getToken().await()
+ Log.d(TAG, "FCM registration Token: ${_token.value}")
+ } catch(e: Exception) {
+ Log.w(TAG, "Fetching FCM registration token failed", e)
+ }
+ }
+ }
+
+ fun subscribeToTopic(topicName: String) {
+ viewModelScope.launch {
+ Log.d(TAG, "Subscribing to topic: $topicName")
+ try {
+ messaging.subscribeToTopic(topicName).await()
+ Log.d(TAG, "Subscribed to $topicName topic")
+ _subscriptionState.value = SubscriptionState.Success
+ } catch (e: Exception) {
+ Log.w(TAG, "Failed to subscribe to $topicName", e)
+ _subscriptionState.value = SubscriptionState.Failed
+ }
+ }
+ }
+
+ companion object {
+ const val TAG = "FCMViewModel"
+ // Used to inject this ViewModel's dependencies
+ // See also: https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-factories
+ val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
+ @Suppress("UNCHECKED_CAST")
+ override fun create(
+ modelClass: Class,
+ extras: CreationExtras
+ ): T {
+ val firebaseMessaging = FirebaseMessaging.getInstance()
+ return FirebaseMessagingViewModel(firebaseMessaging) as T
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt
index 2db8171f52..c85adc0327 100644
--- a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt
+++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/MainActivity.kt
@@ -1,23 +1,20 @@
package com.google.firebase.quickstart.fcm.kotlin
-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 android.util.Log
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.content.ContextCompat
-import com.google.android.gms.tasks.OnCompleteListener
-import com.google.firebase.ktx.Firebase
-import com.google.firebase.messaging.ktx.messaging
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.snackbar.Snackbar
import com.google.firebase.quickstart.fcm.R
import com.google.firebase.quickstart.fcm.databinding.ActivityMainBinding
+import com.google.firebase.quickstart.fcm.kotlin.data.SubscriptionState
+import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
+ private val viewModel: FirebaseMessagingViewModel by viewModels { FirebaseMessagingViewModel.Factory }
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
@@ -38,11 +35,11 @@ class MainActivity : AppCompatActivity() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create channel to show notifications.
- val channelId = getString(R.string.default_notification_channel_id)
- val channelName = getString(R.string.default_notification_channel_name)
- val notificationManager = getSystemService(NotificationManager::class.java)
- notificationManager?.createNotificationChannel(NotificationChannel(channelId,
- channelName, NotificationManager.IMPORTANCE_LOW))
+ viewModel.setNotificationChannel(
+ this,
+ getString(R.string.default_notification_channel_id),
+ getString(R.string.default_notification_channel_name)
+ )
}
// If a notification message is tapped, any data accompanying the notification
@@ -54,69 +51,54 @@ class MainActivity : AppCompatActivity() {
//
// Handle possible data accompanying notification message.
// [START handle_data_extras]
- intent.extras?.let {
- for (key in it.keySet()) {
- val value = intent.extras?.get(key)
- Log.d(TAG, "Key: $key Value: $value")
- }
- }
+ viewModel.logNotificationData(intent)
// [END handle_data_extras]
binding.subscribeButton.setOnClickListener {
- Log.d(TAG, "Subscribing to weather topic")
// [START subscribe_topics]
- Firebase.messaging.subscribeToTopic("weather")
- .addOnCompleteListener { task ->
- var msg = getString(R.string.msg_subscribed)
- if (!task.isSuccessful) {
- msg = getString(R.string.msg_subscribe_failed)
- }
- Log.d(TAG, msg)
- Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
- }
+ viewModel.subscribeToTopic("weather")
// [END subscribe_topics]
}
binding.logTokenButton.setOnClickListener {
- // Get token
// [START log_reg_token]
- Firebase.messaging.getToken().addOnCompleteListener(OnCompleteListener { task ->
- if (!task.isSuccessful) {
- Log.w(TAG, "Fetching FCM registration token failed", task.exception)
- return@OnCompleteListener
- }
-
- // Get new FCM registration token
- val token = task.result
-
- // Log and toast
- val msg = getString(R.string.msg_token_fmt, token)
- Log.d(TAG, msg)
- Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
- })
+ viewModel.getToken()
// [END log_reg_token]
}
- Toast.makeText(this, "See README for setup instructions", Toast.LENGTH_SHORT).show()
- askNotificationPermission()
- }
+ lifecycleScope.launch {
+ viewModel.token.collect { token ->
+ if(token.isNotEmpty()){
+ val msg = getString(R.string.msg_token_fmt, token)
+ Snackbar.make(findViewById(android.R.id.content),
+ msg, Snackbar.LENGTH_LONG).show()
+ }
+ }
+ }
- private fun askNotificationPermission() {
- // This is only necessary for API Level > 33 (TIRAMISU)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==
- PackageManager.PERMISSION_GRANTED
- ) {
- // FCM SDK (and your app) can post notifications.
- } else {
- // Directly ask for the permission
- requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
+ lifecycleScope.launch {
+ viewModel.subscriptionState.collect { state ->
+ when (state) {
+ SubscriptionState.Success -> {
+ Snackbar.make(findViewById(android.R.id.content),
+ getString(R.string.msg_subscribed), Snackbar.LENGTH_LONG).show()
+ }
+ SubscriptionState.Failed -> {
+ Snackbar.make(findViewById(android.R.id.content),
+ getString(R.string.msg_subscribe_failed), Snackbar.LENGTH_LONG).show()
+ }
+ SubscriptionState.Loading -> { }
+ }
}
}
+ Toast.makeText(this, getString(R.string.msg_setup_readme_instructions), Toast.LENGTH_SHORT).show()
+
+ lifecycleScope.launch {
+ viewModel.askNotificationPermission(applicationContext, requestPermissionLauncher)
+ }
}
companion object {
-
private const val TAG = "MainActivity"
}
}
diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/data/SubscriptionState.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/data/SubscriptionState.kt
new file mode 100644
index 0000000000..b50466f43f
--- /dev/null
+++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/data/SubscriptionState.kt
@@ -0,0 +1,5 @@
+package com.google.firebase.quickstart.fcm.kotlin.data
+
+enum class SubscriptionState {
+ Success, Failed, Loading
+}
\ No newline at end of file
diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ui/theme/Color.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ui/theme/Color.kt
new file mode 100644
index 0000000000..20343e40cc
--- /dev/null
+++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ui/theme/Color.kt
@@ -0,0 +1,11 @@
+package com.google.firebase.quickstart.fcm.kotlin.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val FirebaseBlue = Color(0xFF0288D1) // copied from colors.xml
+val FirebaseBannerBlue = Color(0xFF039BE5) // copied from colors.xml
+val FirebaseOrange = Color(0xFFFFA000) // copied from colors.xml
\ No newline at end of file
diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ui/theme/Shape.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ui/theme/Shape.kt
new file mode 100644
index 0000000000..e9c74dea92
--- /dev/null
+++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ui/theme/Shape.kt
@@ -0,0 +1,11 @@
+package com.google.firebase.quickstart.fcm.kotlin.ui.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Shapes
+import androidx.compose.ui.unit.dp
+
+val Shapes = Shapes(
+ small = RoundedCornerShape(16.dp),
+ medium = RoundedCornerShape(2.dp),
+ large = RoundedCornerShape(0.dp)
+)
\ No newline at end of file
diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ui/theme/Theme.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ui/theme/Theme.kt
new file mode 100644
index 0000000000..c0284b207d
--- /dev/null
+++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ui/theme/Theme.kt
@@ -0,0 +1,38 @@
+package com.google.firebase.quickstart.fcm.kotlin.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.darkColors
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+
+private val DarkColorPalette = darkColors(
+ primary = Purple80,
+ primaryVariant = PurpleGrey80,
+ secondary = Pink80
+)
+
+private val LightColorPalette = lightColors(
+ primary = FirebaseBlue,
+ primaryVariant = FirebaseBannerBlue,
+ secondary = FirebaseOrange
+)
+
+@Composable
+fun FirebaseMessagingTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit
+) {
+ val colors = if (darkTheme) {
+ DarkColorPalette
+ } else {
+ LightColorPalette
+ }
+
+ MaterialTheme(
+ colors = colors,
+ typography = Typography,
+ shapes = Shapes,
+ content = content
+ )
+}
\ No newline at end of file
diff --git a/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ui/theme/Type.kt b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ui/theme/Type.kt
new file mode 100644
index 0000000000..355ec16f6a
--- /dev/null
+++ b/messaging/app/src/main/java/com/google/firebase/quickstart/fcm/kotlin/ui/theme/Type.kt
@@ -0,0 +1,18 @@
+package com.google.firebase.quickstart.fcm.kotlin.ui.theme
+
+import androidx.compose.material.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ body1 = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ )
+)
\ No newline at end of file
diff --git a/messaging/app/src/main/res/values/strings.xml b/messaging/app/src/main/res/values/strings.xml
index aa1617c3e3..633f6c52bd 100644
--- a/messaging/app/src/main/res/values/strings.xml
+++ b/messaging/app/src/main/res/values/strings.xml
@@ -19,4 +19,9 @@
Failed to subscribe to weather topic
FCM Message
+
+ Notifications permission granted
+ FCM can\'t post notifications without POST_NOTIFICATIONS permission
+
+ See README for setup instructions"
diff --git a/messaging/gradle.properties b/messaging/gradle.properties
index aac7c9b461..29b531a1d1 100644
--- a/messaging/gradle.properties
+++ b/messaging/gradle.properties
@@ -10,7 +10,7 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
-
+android.useAndroidX=true
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects