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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 06:39:33