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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 13:35:33