ViewModel重建后Compose StateFlow短暂保留旧状态引发崩溃问题
Compose-JB 重建时StateFlow残留旧值引发数组越界崩溃问题
问题详情
- 基础实现:定义持有StateFlow的ViewModel,该ViewModel在特定场景触发重建时,会将内部StateFlow的初始值设置为0;同时实现Compose视图,读取该StateFlow的值并根据值渲染对应文本内容。
- 复现步骤:先将StateFlow的状态值修改为2,随后触发Compose视图与关联ViewModel整体重建。
- 异常现象:重建过程中,尽管ViewModel随视图一同完成重建且StateFlow初始值已明确设置为0,StateFlow仍会在极短时间内保留旧的状态值2,之后才切换为正确的初始值0。
- 故障影响:若重建时ViewModel持有的列表长度与旧状态对应的索引不匹配,就会读取不存在的列表索引,触发数组越界导致应用崩溃。例如将ViewModelTwo构造参数从
ViewModelTwo(mutableListOf("text4", "text5", "text6"))改为ViewModelTwo(mutableListOf("text4"))时就会触发崩溃,查看日志可明确观测到状态先输出旧值2,之后才输出默认值0的异常流转过程。
注:因发布平台未提供Compose-JB相关标签,暂使用Android Compose标签发布问题。
复现代码
Main.kt
@Composable @Preview fun App(viewModelOne: ViewModelOne) { val showComposable by viewModelOne.stateOne.collectAsState() MaterialTheme { // 根据状态判断创建不同的ViewModel实例 val viewModelTwo: ViewModelTwo = when (showComposable) { 0 -> ViewModelTwo(mutableListOf("text1", "text2", "text3")) 1 -> ViewModelTwo(mutableListOf("text4")) else -> ViewModelTwo(mutableListOf("blah1", "blah2", "blah3")) } // 新组件始终伴随默认索引为0的新ViewModelTwo创建,但应用依然会崩溃 TestComposableTwo(viewModelTwo) Row { Button(onClick = { viewModelOne.changeState() }) { Text("先点下方按钮,再点我") } } } } fun main() = application { Window(onCloseRequest = ::exitApplication) { val viewModelOne = ViewModelOne(); App(viewModelOne) } }
TestComposableView.kt
@Composable fun TestComposableTwo(viewModelTwo: ViewModelTwo) { val currentIndex by viewModelTwo.currentListItem.collectAsState() println("当前索引为: $currentIndex") Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxWidth() .fillMaxHeight() ) { // 重建该组件时,currentIndex会先保留旧值,之后才切换为新值 // 新列表不存在索引为2的元素,因此会触发崩溃 Text(text = viewModelTwo.stringList[currentIndex]) Button(onClick = { viewModelTwo.changeIndex() }) { Text("点上方按钮之前先点我") } } }
ViewModel1.kt
class ViewModelOne { private val viewModelScope = CoroutineScope(Dispatchers.IO) private val _stateOne = MutableStateFlow(0) val stateOne = _stateOne.asStateFlow() fun changeState() { viewModelScope.launch { val currentValue = stateOne.value + 1 _stateOne.emit(currentValue) } } }
ViewModel2.kt
class ViewModelTwo(val stringList: List<String>) { private val viewModelScope = CoroutineScope(Dispatchers.IO) private val _currentListItem = MutableStateFlow(0) val currentListItem = _currentListItem.asStateFlow() fun changeIndex() { viewModelScope.launch { _currentListItem.emit(2) } } }
内容的提问来源于stack exchange,提问作者Bojann
相关产品推荐
相关产品推荐

