如何基于MVVM、UseCase、Hilt监听日期/时区变更更新UI
问题解决:时区/日期变更时自动更新Home数据与UI
核心问题分析
TimeZoneBroadcastMonitor未实现currentDateFlow,无法监听系统日期变化- ViewModel中
selectedDate仅在初始化时计算,未响应时区/日期变更自动更新 uiState组合逻辑未关联selectedDate的动态变化,导致UseCase不会重新执行
步骤1:完善TimeZoneBroadcastMonitor,实现日期监听
补充currentDate Flow,监听系统日期、时间变化的广播:
@Singleton class TimeZoneBroadcastMonitor @Inject constructor( @ApplicationContext private val context: Context, @ApplicationScope appScope: CoroutineScope, @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, ) : TimeZoneMonitor { // 原currentTimeZone实现保持不变... override val currentDate: SharedFlow<LocalDateTime> = callbackFlow { // 发送初始日期 sendCurrentLocalDateTime() val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { Intent.ACTION_TIME_CHANGED, Intent.ACTION_DATE_CHANGED, Intent.ACTION_TIMEZONE_CHANGED -> sendCurrentLocalDateTime() } } private fun sendCurrentLocalDateTime() { val currentDateTime = LocalDateTime.now(TimeZone.currentSystemDefault()) trySend(currentDateTime) } } val filter = IntentFilter().apply { addAction(Intent.ACTION_TIME_CHANGED) addAction(Intent.ACTION_DATE_CHANGED) addAction(Intent.ACTION_TIMEZONE_CHANGED) } context.registerReceiver(receiver, filter) awaitClose { context.unregisterReceiver(receiver) } } .distinctUntilChanged() .conflate() .flowOn(ioDispatcher) .shareIn(appScope, SharingStarted.WhileSubscribed(5_000), 1) }
步骤2:重构HomeViewModel,响应时区/日期变化更新selectedDate
移除初始化时静态计算的日期逻辑,改为监听currentDate Flow自动更新selectedDate,同时保留SavedStateHandle的持久化:
@HiltViewModel class HomeViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val analyticsHelper: AnalyticsHelper, timeZoneMonitor: TimeZoneMonitor, val userDataRepository: UserDataRepository, private val getHomeUseCase: GetHomeUseCase, ) : ViewModel() { private val selectedTopicIdKey = "selectedTopicIdKey" private val route: UniversalisRoute = savedStateHandle.toRoute() private val selectedTopicId = savedStateHandle.getStateFlow( key = selectedTopicIdKey, initialValue = route.initialTopicId.toString(), ) // 监听时区变化 val currentTimeZone = timeZoneMonitor.currentTimeZone .stateIn( viewModelScope, SharingStarted.WhileSubscribed(5_000), TimeZone.currentSystemDefault(), ) // 监听日期变化,并同步到SavedStateHandle private val currentDate = timeZoneMonitor.currentDate .stateIn( viewModelScope, SharingStarted.WhileSubscribed(5_000), LocalDateTime.now(TimeZone.currentSystemDefault()) ) // 自动更新的selectedDate:由currentDate驱动,同时持久化到SavedStateHandle private val selectedDate = combine( currentDate, savedStateHandle.getStateFlow("date", 0) ) { date, savedDate -> val newDate = date.format(DateTimeFormatter.ofPattern("yyyyMMdd")).toInt() // 如果是初始值(0)或者日期变化,更新SavedStateHandle if (savedDate == 0 || newDate != savedDate) { savedStateHandle["date"] = newDate } newDate } .stateIn( viewModelScope, SharingStarted.WhileSubscribed(5_000), LocalDateTime.now(TimeZone.currentSystemDefault()) .format(DateTimeFormatter.ofPattern("yyyyMMdd")) .toInt() ) // 重构uiState:使用flatMapLatest,当selectedDate变化时重新执行UseCase val uiState: StateFlow<HomeUiState> = selectedDate .flatMapLatest { date -> combine( selectedTopicId, getHomeUseCase.invoke(date), ) { topicId, homeResource -> HomeUiState.HomeData( selectedTopicId = topicId, selectedDate = date, topics = homeResource ) } } .catch { throwable -> val error = HomeUiState.HomeError( date = selectedDate.value, message = throwable.message ?: "加载失败" ) analyticsHelper.logHomeErrorEvent(error) emit(error) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = HomeUiState.Loading, ) }
关键修改点说明
- 用
currentDateFlow驱动selectedDate,确保时区/日期变化时自动更新日期值 - 使用
flatMapLatest替代原combine:当selectedDate变化时,会自动取消之前的UseCase调用并执行新的查询 - 保留SavedStateHandle的持久化逻辑,确保配置变更后日期状态不丢失
步骤3:优化UI层状态接收(可选)
原HomeScreen已经接收currentTimeZone和currentDate,无需额外修改,但可以确保在Compose中正确观察State:
@ExperimentalLayoutApi @Composable fun HomeItems( uiState: HomeUiState, onTopicClick: (String) -> Unit, modifier: Modifier, haveDate: Boolean = false, currentTimeZone: State<TimeZone>, currentDate: State<LocalDateTime> ) { if (uiState is HomeUiState.HomeData) { Text( text = uiState.topics.data.fecha, modifier = Modifier.padding(2.dp), textAlign = TextAlign.Center, ) // 可选:显示当前系统日期用于调试 Text( text = "当前系统日期:${currentDate.value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))}", modifier = Modifier.padding(4.dp) ) } }
额外注意事项
- 确保
isDateValid()函数能正确校验转换后的日期格式 getHomeUseCase中的universalisRepository.getUniversalisForTest(date).first()如果是阻塞调用,建议改为Flow形式,避免阻塞协程- 测试时可以通过系统设置手动修改时区/日期,验证数据是否自动更新
内容的提问来源于stack exchange,提问作者A. Cedano
相关产品推荐
相关产品推荐

