如何在接收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
相关产品推荐
相关产品推荐

