Jetpack Compose中ViewModel init块读取mutableState值报错解决
问题
在Jetpack Compose开发中,在ViewModel内定义mutableStateOf状态后,在init初始化块中读取状态值时抛出异常:IllegalStateException: Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied,对应实现代码如下:
@HiltViewModel class EventPageVM @Inject constructor( private val repository: EventRepository, private val noteRepository: NoteRepository ) : ViewModel() { private val _eventsList = MutableStateFlow<List<Event>>(emptyList()) val eventsList = _eventsList.asStateFlow() var showAllEvent = mutableStateOf(false) var showPastEvent = mutableStateOf(true) init { viewModelScope.launch(Dispatchers.IO) { val fromTo = Extra.getFromToNextMonth(CivilDate()) val start= if (showPastEvent.value)null else fromTo[0] val end=if (showAllEvent.value)null else fromTo[1] repository.getEvents(start, end).distinctUntilChanged().collect { result -> _eventsList.value = result } } } }
需要实现在init块中正常读取mutableState值的效果。
异常原因
这个异常是Jetpack Compose快照系统的校验机制触发的:mutableStateOf创建的状态归属于Compose快照系统,新创建的状态必须先提交到当前生效的全局快照后才能被安全读取。viewModelScope启动的IO调度协程不属于快照感知的执行上下文,协程实际执行的时机如果早于状态首次提交到全局快照的时机,就会抛出该错误。
另外你当前的实现还存在逻辑缺陷:init块中只会读取一次状态的初始值,后续showAllEvent/showPastEvent的值变更时,不会重新触发事件列表的拉取逻辑。
解决方案
根据你的业务场景选对应方案即可:
- 方案1:需要状态变更时自动刷新列表(推荐)
用Compose提供的snapshotFlowAPI包裹状态读取逻辑,该API内部会自动处理快照上下文适配,同时可以监听状态变化自动重跑数据拉取逻辑,从根源避免异常,还能补全状态联动刷新的能力,修改后代码如下:init { viewModelScope.launch(Dispatchers.IO) { // 监听两个状态的任意变更,自动触发刷新 snapshotFlow { showAllEvent.value to showPastEvent.value } .flatMapLatest { (showAll, showPast) -> val fromTo = Extra.getFromToNextMonth(CivilDate()) val start = if (showPast) null else fromTo[0] val end = if (showAll) null else fromTo[1] repository.getEvents(start, end).distinctUntilChanged() } .collect { result -> _eventsList.value = result } } } - 方案2:仅需要在初始化时读取一次状态初始值,不需要后续联动刷新
把状态的初始值抽为同作用域的常量,初始化协程中直接使用常量值即可,不需要主动读取state.value,避免触发快照校验:// 抽离初始值常量 private const val DEFAULT_SHOW_ALL = false private const val DEFAULT_SHOW_PAST = true var showAllEvent = mutableStateOf(DEFAULT_SHOW_ALL) var showPastEvent = mutableStateOf(DEFAULT_SHOW_PAST) init { viewModelScope.launch(Dispatchers.IO) { val fromTo = Extra.getFromToNextMonth(CivilDate()) val start= if (DEFAULT_SHOW_PAST) null else fromTo[0] val end=if (DEFAULT_SHOW_ALL) null else fromTo[1] repository.getEvents(start, end).distinctUntilChanged().collect { result -> _eventsList.value = result } } } - 方案3:临时修复快照读取问题
把读取状态的逻辑包裹在Snapshot.withMutableSnapshot {}块中执行,手动提供快照上下文即可避免异常,但该方案不会解决状态变更不刷新列表的逻辑问题:init { viewModelScope.launch(Dispatchers.IO) { Snapshot.withMutableSnapshot { val fromTo = Extra.getFromToNextMonth(CivilDate()) val start= if (showPastEvent.value)null else fromTo[0] val end=if (showAllEvent.value)null else fromTo[1] repository.getEvents(start, end).distinctUntilChanged().collect { result -> _eventsList.value = result } } } }
内容的提问来源于stack exchange,提问作者abbasalim
相关产品推荐
相关产品推荐

