You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在接收Firebase通知后在Composable中显示BottomSheet并传递数据

把FCM通知数据传到Composable页面的解决办法

核心问题在哪

你现在的代码里,FirebaseNotificationService里自己初始化的FirebaseViewModel和OrderViewModel,跟Composable页面里用viewModel()拿到的ViewModel根本不是同一个对象,所以数据传不过去是必然的。

一步步解决

1. 先统一通知数据的格式

搞个密封类,把不同类型的通知数据都装进去,避免混乱:

sealed class NotificationEvent {
    // 对应FCM弹出的通知栏消息
    data class FcmBanner(val title: String, val body: String) : NotificationEvent()
    // 对应FCM的纯数据消息
    data class NewOrder(val data: NotificationDataModel) : NotificationEvent()
}

2. 让Repository做数据中转

用SharedFlow在Repository里做事件分发,Service把数据扔给Repository,ViewModel从Repository拿,这样组件之间就解耦了:

class NotificationRepository {
    // 内部用MutableSharedFlow发事件,外部只能用SharedFlow订阅
    private val _notificationEvents = MutableSharedFlow<NotificationEvent>()
    val notificationEvents = _notificationEvents.asSharedFlow()

    // 发送通知事件的方法
    suspend fun postNotificationEvent(event: NotificationEvent) {
        _notificationEvents.emit(event)
    }
}

3. 改造ViewModel,把事件转成Composable能观察的状态

ViewModel负责订阅Repository的事件,然后转成StateFlow,让Composable可以实时监听:

class NotificationViewModel(
    private val repository: NotificationRepository
) : ViewModel() {
    // 存当前最新的通知数据
    private val _currentNotification = MutableStateFlow<NotificationEvent?>(null)
    val currentNotification = _currentNotification.asStateFlow()

    init {
        // 启动协程监听Repository的事件
        viewModelScope.launch {
            repository.notificationEvents.collect { event ->
                _currentNotification.value = event
                // 要是想保留历史通知,就把上面改成List类型的StateFlow,每次追加新事件
                // _notificationList.value = _notificationList.value + event
            }
        }
    }
}

4. 修改Service,通过Repository发数据

Service里别直接操作ViewModel了,改成把数据扔给Repository就行:

class FireBaseNotificationServices(
    private val notificationRepository: NotificationRepository = Graph.notificationRepository
) : FirebaseMessagingService() {

    private val scope = CoroutineScope(Dispatchers.IO)

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        Log.d(TAG, "From: ${remoteMessage.from}")

        // 处理纯数据消息
        if (remoteMessage.data.isNotEmpty()) {
            Log.d(TAG, "Message data payload: ${remoteMessage.data}")
            val orderData = NotificationDataModel(
                id = 0,
                notification_type = "new order",
                notification_data = remoteMessage.data.toString()
            )
            // 给Repository发订单事件
            scope.launch {
                notificationRepository.postNotificationEvent(NotificationEvent.NewOrder(orderData))
            }

            if (needsToBeScheduled()) {
                // scheduleJob(remoteMessage.data)
            } else {
                handleNow()
            }
        }

        // 处理通知栏消息
        remoteMessage.notification?.let {
            Log.d(TAG, "Message Notification Title: ${it.title}")
            Log.d(TAG, "Message Notification Body: ${it.body}")
            sendNotification(it.body.toString())
            // 给Repository发通知栏事件
            scope.launch {
                notificationRepository.postNotificationEvent(
                    NotificationEvent.FcmBanner(it.title.orEmpty(), it.body.orEmpty())
                )
            }
        }
    }

    // ... 原来的sendNotification、onNewToken这些方法都不用改

    companion object {
        private const val TAG = "MyFirebaseMsgService"
    }
}

5. 在Composable页面展示数据

在需要显示通知的页面里,拿到ViewModel,然后收集它的currentNotification状态就行:

@Composable
fun NotificationScreen(
    viewModel: NotificationViewModel = viewModel() // 这里用Hilt或者ViewModelProvider拿实例
) {
    val currentNotification by viewModel.currentNotification.collectAsState()

    Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
        currentNotification?.let { event ->
            when (event) {
                is NotificationEvent.FcmBanner -> {
                    Text(text = "收到通知:", fontSize = 18.sp, fontWeight = FontWeight.Bold)
                    Text(text = "标题:${event.title}", fontSize = 16.sp)
                    Text(text = "内容:${event.body}", fontSize = 16.sp)
                }
                is NotificationEvent.NewOrder -> {
                    Text(text = "新订单通知:", fontSize = 18.sp, fontWeight = FontWeight.Bold)
                    Text(text = "类型:${event.data.notification_type}", fontSize = 16.sp)
                    Text(text = "数据:${event.data.notification_data}", fontSize = 16.sp)
                }
            }
        } ?: run {
            Text(text = "暂无通知", fontSize = 16.sp)
        }
    }
}

要注意的点

  • 确保NotificationRepository是单例(比如用Hilt注入,或者手动写单例),这样Service和ViewModel用的是同一个实例
  • Service里用CoroutineScope处理协程,别在主线程做耗时操作
  • 如果需要保存历史通知,把ViewModel里的_currentNotification改成MutableStateFlow<List<NotificationEvent>>,每次新事件来就加到列表里

内容的提问来源于stack exchange,提问作者Gurjeet Singh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 05:07:35