如何强制更新MutableStateFlow?解决日期变更时UI不更新问题
问题描述
我正在开发每日挑战页面,当应用处于打开状态时日期发生变更(例如从2022年11月7日23:59到11月8日00:00),页面状态无法随之更新。我的实现代码如下:
data class MyObject(val startDate: Date, val lastVote: List<Date> = emptyList()) { fun isChallengeDone(): Boolean { return Calendar.getInstance().time >= startDate } } @HiltViewModel class DailyChallengeViewModel: ViewModel() { private val _currentDay = MutableStateFlow(Calendar.getInstance()) private val _myObject = MutableStateFlow(MyObject( Calendar.getInstance().apply { set(Calendar.DATE, 8) set(Calendar.HOUR_OF_DAY, 0) set(Calendar.MINUTE , 0) set(Calendar.SECOND , 0) set(Calendar.MILLISECOND , 0) }.time )) val myObject = _myObject.asStateFlow() init { _myObject.combine(_currentDay) { obj, _ -> _myObject.value = obj }.launchIn(viewModelScope) } fun updateDate() { _currentDay.value = Calendar.getInstance() } } @Composable fun DailyChallengeScreen(viewModel: DailyChallengeViewModel = hiltViewModel()) { val context = LocalContext.current DisposableEffect(context) { val receiver = object: BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { viewModel.updateDate() } } context.registerReceiver(receiver, IntentFilter(Intent.ACTION_DATE_CHANGED)) onDispose { context.unregisterReceiver(receiver) } } val myObject by viewModel.myObject.collectAsStateWithLifecycle() DailyChallengeContent(myObject) } @Composable fun DailyChallengeContent(myObject: MyObject) { Column(verticalArrangement = Arrangement.spacedBy(20.dp)) { Text("${Calendar.getInstance().time} >= ${myObject.startDate}") Text(myObject.isChallengeDone().toString()) } }
我知道_myObject.combine(_currentDay) { obj, _ -> _myObject.value = obj }无法触发更新,因为_myObject.value与obj原子相等,请问如何实现日期变更时更新对应的Composable?
解决方案
方案1:将挑战完成状态转为派生状态
isChallengeDone的结果依赖实时时间,没必要绑定在MyObject实例中,直接在ViewModel里通过currentDay和myObject组合出派生状态即可:
修改ViewModel
@HiltViewModel class DailyChallengeViewModel: ViewModel() { private val _currentDay = MutableStateFlow(Calendar.getInstance()) private val _myObject = MutableStateFlow(MyObject( Calendar.getInstance().apply { set(Calendar.DATE, 8) set(Calendar.HOUR_OF_DAY, 0) set(Calendar.MINUTE , 0) set(Calendar.SECOND , 0) set(Calendar.MILLISECOND , 0) }.time )) val myObject = _myObject.asStateFlow() // 派生状态:根据当前时间和myObject计算是否完成挑战 val isChallengeDone = combine(myObject, _currentDay) { obj, _ -> obj.isChallengeDone() }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = _myObject.value.isChallengeDone() ) fun updateDate() { _currentDay.value = Calendar.getInstance() } }
修改Composable
@Composable fun DailyChallengeScreen(viewModel: DailyChallengeViewModel = hiltViewModel()) { val context = LocalContext.current DisposableEffect(context) { val receiver = object: BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { viewModel.updateDate() } } context.registerReceiver(receiver, IntentFilter(Intent.ACTION_DATE_CHANGED)) onDispose { context.unregisterReceiver(receiver) } } val myObject by viewModel.myObject.collectAsStateWithLifecycle() val isDone by viewModel.isChallengeDone.collectAsStateWithLifecycle() DailyChallengeContent(myObject, isDone) } @Composable fun DailyChallengeContent(myObject: MyObject, isChallengeDone: Boolean) { Column(verticalArrangement = Arrangement.spacedBy(20.dp)) { Text("${Calendar.getInstance().time} >= ${myObject.startDate}") Text(isChallengeDone.toString()) } }
方案2:强制更新MyObject实例
如果要保留MyObject中的isChallengeDone方法,可以在日期变更时,通过data class的copy()方法创建新实例,触发StateFlow的更新:
修改ViewModel
@HiltViewModel class DailyChallengeViewModel: ViewModel() { private val _currentDay = MutableStateFlow(Calendar.getInstance()) private val _myObject = MutableStateFlow(MyObject( Calendar.getInstance().apply { set(Calendar.DATE, 8) set(Calendar.HOUR_OF_DAY, 0) set(Calendar.MINUTE , 0) set(Calendar.SECOND , 0) set(Calendar.MILLISECOND , 0) }.time )) val myObject = _myObject.asStateFlow() init { // 监听日期变更,通过copy创建新实例触发更新 _currentDay.onEach { _myObject.value = _myObject.value.copy() }.launchIn(viewModelScope) } fun updateDate() { _currentDay.value = Calendar.getInstance() } }
这样日期变更时,_myObject会发射新的实例,Composable会自动收集状态并刷新。
补充建议
- 优先使用
java.time包下的API(如LocalDate、Instant)替代Calendar和Date,它们线程安全且API设计更简洁。 ACTION_DATE_CHANGED广播无法覆盖所有时间变更场景(比如用户手动修改系统时间),如果需要更全面的监听,可以额外注册Intent.ACTION_TIME_CHANGED广播。
内容的提问来源于stack exchange,提问作者Biscuit
相关产品推荐
相关产品推荐

