如何在ViewModel中维护依赖MutableStateFlow的密封类变体状态
解决密封类变体状态在屏幕旋转时丢失的问题
问题根源
你当前的实现中,test流是通过choose.map生成的——每次choose切换时,都会创建全新的TestSealed实例,而这些实例的状态(比如Test1的n、Test2的s)并没有被ViewModel持有。屏幕旋转时,UI重新收集test流,会触发map生成新实例,之前修改的状态自然就丢失了。
方案一:Lazy初始化变体状态,通过密封类暴露操作接口
这种方式只为当前激活过的变体创建状态,未使用的变体不会提前占用资源,同时所有状态都由ViewModel持久化。
class TestViewModel : ViewModel() { enum class TestEnum { Test1, Test2 } private val _choose = MutableStateFlow(TestEnum.Test1) val choose = _choose.asStateFlow() // 密封类封装状态流和更新方法,让UI可以直接使用 sealed class TestSealed { data class Test1(val n: StateFlow<Int>, val updateN: (Int) -> Unit) : TestSealed() data class Test2(val s: StateFlow<String>, val updateS: (String) -> Unit) : TestSealed() } // Lazy初始化,只有第一次用到对应变体时才创建状态流 private val _test1State = lazy { MutableStateFlow(0) } private val _test2State = lazy { MutableStateFlow("asd") } val test = choose.map { currentChoice -> when(currentChoice) { TestEnum.Test1 -> { val state = _test1State.value TestSealed.Test1(state) { newN -> state.value = newN } } TestEnum.Test2 -> { val state = _test2State.value TestSealed.Test2(state) { newS -> state.value = newS } } } }.stateIn( viewModelScope, SharingStarted.WhileSubscribed(5000), // 初始值用lazy的默认状态 TestSealed.Test1(_test1State.value) { _test1State.value.value = it } ) fun toggle() { _choose.update { when(it) { TestEnum.Test1 -> TestEnum.Test2 TestEnum.Test2 -> TestEnum.Test1 } } } } @Preview @Composable fun TestPreview() { AppTheme(true) { Surface(Modifier.fillMaxSize()) { val vm = viewModel<TestViewModel>() Column { Button(onClick = { vm.toggle() }) { Text("toggle") } val test by vm.test.collectAsState() when (val t = test) { is TestViewModel.TestSealed.Test1 -> { val n by t.n.collectAsState() Button(onClick = { t.updateN(n + 1) }) { Text("increment") } Text("Test1 $n") } is TestViewModel.TestSealed.Test2 -> { val s by t.s.collectAsState() Button(onClick = { t.updateS("foo") }) { Text("foo") } Text("Test2 $s") } } } } } }
方案优势
- 未激活的变体状态不会提前初始化,节省资源
- 所有状态由ViewModel持有,屏幕旋转时不会丢失
- 密封类封装了状态观察和更新逻辑,UI层调用更清晰
方案二:用Map缓存变体状态实例
这种方式直接缓存每个变体的完整状态实例,切换时复用已有实例,逻辑更直观。
class TestViewModel : ViewModel() { enum class TestEnum { Test1, Test2 } private val _choose = MutableStateFlow(TestEnum.Test1) val choose = _choose.asStateFlow() sealed class TestSealed { class Test1(val n: MutableStateFlow<Int>) : TestSealed() class Test2(val s: MutableStateFlow<String>) : TestSealed() } // 缓存每个变体的状态实例,确保切换时复用 private val variantStates = mutableMapOf<TestEnum, TestSealed>() private val _test = MutableStateFlow<TestSealed>(TestSealed.Test1(MutableStateFlow(0))) val test = _test.asStateFlow() init { // 初始化默认状态到缓存 variantStates[TestEnum.Test1] = _test.value // 监听选择变化,更新当前显示的状态实例 viewModelScope.launch { choose.collect { choice -> _test.value = variantStates.getOrPut(choice) { // 缓存中没有时创建新实例 when(choice) { TestEnum.Test1 -> TestSealed.Test1(MutableStateFlow(0)) TestEnum.Test2 -> TestSealed.Test2(MutableStateFlow("asd")) } } } } } fun toggle() { _choose.update { when(it) { TestEnum.Test1 -> TestEnum.Test2 TestEnum.Test2 -> TestEnum.Test1 } } } } @Preview @Composable fun TestPreview() { AppTheme(true) { Surface(Modifier.fillMaxSize()) { val vm = viewModel<TestViewModel>() Column { Button(onClick = { vm.toggle() }) { Text("toggle") } val test by vm.test.collectAsState() when (val t = test) { is TestViewModel.TestSealed.Test1 -> { val n by t.n.collectAsState() Button(onClick = { t.n.update { n + 1 } }) { Text("increment") } Text("Test1 $n") } is TestViewModel.TestSealed.Test2 -> { val s by t.s.collectAsState() Button(onClick = { t.s.update { "foo" } }) { Text("foo") } Text("Test2 $s") } } } } } }
方案优势
- 逻辑更简单直观,直接复用已有状态实例
- 状态完全由ViewModel控制,旋转时不丢失
- UI层代码无需修改,和原实现保持一致
内容的提问来源于stack exchange,提问作者ultrapoci
相关产品推荐
相关产品推荐

