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

Kotlin药品提醒APP:BroadcastReceiver触发时Flow未更新问题

问题:药品提醒APP中ViewModel对AlarmReceiver的Flow更新无响应

我是Kotlin新手,正在开发课程作业——药品提醒APP。发现ViewModel在AlarmReceiver向currentAlerts Flow添加新名称时无响应,仅APP启动时会打印'doing something',添加名称时无反应。想询问是否可以这样使用Flow,还是我对Flow存在理解误区?

medicineViewModel.kt

class MedicineViewModel(
    private val dao: DatabaseDAO,
    private val alarmScheduler: AlarmScheduler,
    alarmReceiver: AlarmReceiver,
    private val userPreferencesRepository: UserPreferencesRepository,
) : ViewModel() {
    
    private val _currentAlerts = alarmReceiver.currentAlertsFlow

    private val _medicines = combine(dao.getMedicinesByName(), _currentAlerts) { medicines, currentAlerts ->
        Log.d("medicineViewModel", "${currentAlerts.size}")
        medicines.map { medicine ->
            Log.d("medicineViewMode", "doing something! ${medicine.name}")
            Log.d("medicineViewModel", "${medicine.name}, ${medicine.name in currentAlerts}")
            medicine.copy(isAlerting = medicine.name in currentAlerts)
        }
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())

    private val _preferences = userPreferencesRepository.getActivated().stateIn(
        viewModelScope,
        SharingStarted.WhileSubscribed(),
        UserPreferences(activated = false)
    )

    //_state håller koll på allt som visas på skärmen.
    private val _state =
        MutableStateFlow(MedicineState()) // Starta ett nytt flow utifrån MedicineState (börjar tomt)

    // _state är allting som visas på skärmen. Man hämtar medicinerna via DAO och stoppar in det i "medicines" i state.
    // Allting innan .stateIn är instruktioner för när man börjar löpande hämta data. MedicineState() i stateIn är
    // utgångspunkten, alltså en tom skärm.
    val state = combine(_state, _medicines, _preferences) { state, medicines, preferences ->
        state.copy(
            medicines = medicines,
            preferences = preferences,
        )
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), MedicineState())

        fun onEvent(event: MedicineEvent) {
            when (event) {
               ...
           }
        }

    fun calculateAlarms() {
        for (medicine in state.value.medicines) {

            val alarmItem = AlarmItem(
                medicineName = medicine.name,
                time = LocalDateTime.now()
                    .plusMinutes(medicine.interval.toLong())
            )

            alarmItem.let(alarmScheduler::schedule)

        }
    }
}

alarmReceiver.kt

package com.example.myapplication.model.alarm

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

class AlarmReceiver : BroadcastReceiver() {

    // MutableStateFlow to hold the latest list of alerts
    private val _currentAlertsFlow = MutableStateFlow(mutableListOf<String>())
    val currentAlertsFlow: StateFlow<MutableList<String>> get() = _currentAlertsFlow

    override fun onReceive(context: Context, intent: Intent?) {
        Log.d("AlarmReceiver", "alarm triggered!")
        val message = intent?.getStringExtra("medicineName") ?: return
        Log.d("AlarmReceiver", "Alarm triggered: $message")

        // Update the alerts list and emit the new list
//        val updatedAlerts = _currentAlertsFlow.value.toMutableList()
//        updatedAlerts.add(message)
//        _currentAlertsFlow.value = updatedAlerts
        _currentAlertsFlow.value.add(message)
    }
}

问题分析与解决方案

1. StateFlow的核心误解:引用变化才会触发更新

StateFlow只有当value的引用发生改变时,才会向订阅者发射新值。你当前直接调用_currentAlertsFlow.value.add(message),只是修改了现有MutableList的内容,但列表本身的引用没有变,所以StateFlow不会触发更新,ViewModel中的combine也不会重新计算。

修复方式:
恢复你注释掉的代码,通过创建新的列表实例来更新StateFlow:

val updatedAlerts = _currentAlertsFlow.value.toMutableList()
updatedAlerts.add(message)
_currentAlertsFlow.value = updatedAlerts

2. 避免用可变集合作为StateFlow的泛型类型

直接暴露MutableList存在风险,外部可能意外修改列表内容导致数据流不一致。建议将Flow的泛型改为不可变的List<String>:

// AlarmReceiver中修改Flow定义
private val _currentAlertsFlow = MutableStateFlow<List<String>>(emptyList())
val currentAlertsFlow: StateFlow<List<String>> get() = _currentAlertsFlow

// 更新时简化写法
override fun onReceive(context: Context, intent: Intent?) {
    // ... 其他代码
    _currentAlertsFlow.value = _currentAlertsFlow.value + message
}

3. 潜在的BroadcastReceiver实例问题

如果你的AlarmReceiver是静态注册(在AndroidManifest.xml中声明),那么每次广播触发时,系统都会创建一个新的AlarmReceiver实例。这意味着ViewModel持有的Receiver实例和触发广播的Receiver实例不是同一个,两者的currentAlertsFlow完全独立,ViewModel自然收不到更新。

修复方式:

  • 改为动态注册Receiver:在ViewModel或关联的Activity/Fragment中注册Receiver,确保使用的是同一个实例。
  • 或者改用更可靠的跨组件通信方式,比如用Room数据库存储当前的提醒状态,ViewModel通过监听Room的Flow来获取更新;或者使用全局的SharedFlow配合单例Scope传递事件。

内容的提问来源于stack exchange,提问作者Oskar Andersson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 12:24:54