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

合并StateFlows异常:更新state2后mergedState无变化

问题描述

在ViewModel中使用merge合并两个MutableStateFlow(state1和state2)为mergedState,更新state1时mergedState能同步变化,但更新state2时mergedState的值始终不变。

示例代码如下:

ViewModel代码

class FlowViewModel : ViewModel() {

    private val state1 = MutableStateFlow(0)
    private val state2 = MutableStateFlow(0)

    val mergedState = merge(state1, state2)

    fun updateState1(value: Int) {
        state1.value = value
    }

    fun updateState2(value: Int) {
        state2.value = value
    }
}

测试代码

class ExampleUnitTest {

    private lateinit var flowViewModel: FlowViewModel

    @Before
    fun setUp() {
        Dispatchers.setMain(UnconfinedTestDispatcher())
        flowViewModel = FlowViewModel()
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }

    @Test
    fun `test merged flow`() = runTest {
        assertThat(flowViewModel.mergedState.first()).isEqualTo(0)

        flowViewModel.updateState1(1)
        assertThat(flowViewModel.mergedState.first()).isEqualTo(1) // 符合预期

        flowViewModel.updateState2(2)
        assertThat(flowViewModel.mergedState.first()).isEqualTo(2) // 值仍为1,不符合预期
    }
}

无论先更新哪个状态,似乎只有state1的更新能同步到mergedState,需要解决思路。


问题原因及解决思路

核心原因

问题出在测试代码的实现逻辑上:

  • merge合并多个StateFlow时,每次新订阅(比如调用first()),所有上游StateFlow都会先发射各自的当前值,发射顺序严格遵循merge的参数顺序(先state1后state2)。
  • 每次调用first(),都会直接取第一个收到的发射值(也就是state1的当前值),不会等待state2的发射,因此即使state2已经更新,测试也无法捕获到它的变化。

解决方法

方法1:单次订阅收集全量发射值

不要每次验证都重新订阅,而是在测试开始时就持续收集mergedState的所有发射值,再对序列进行验证:

@Test
fun `test merged flow correctly`() = runTest {
    val emissions = mutableListOf<Int>()
    val collectJob = launch {
        flowViewModel.mergedState.collect { emissions.add(it) }
    }

    // 初始阶段:merge会先发射state1的0,再发射state2的0
    assertThat(emissions).containsExactly(0, 0)

    flowViewModel.updateState1(1)
    assertThat(emissions.last()).isEqualTo(1)

    flowViewModel.updateState2(2)
    assertThat(emissions.last()).isEqualTo(2)

    collectJob.cancel()
}

方法2:用take(n)捕获指定次数的发射

如果只需要验证后续的更新动作,可以用take获取特定次数的发射值,跳过初始的重复发射:

@Test
fun `test merged flow with take`() = runTest {
    // 先收集初始的两个发射值,清空订阅
    flowViewModel.mergedState.take(2).toList()

    flowViewModel.updateState1(1)
    assertThat(flowViewModel.mergedState.take(1).first()).isEqualTo(1)

    flowViewModel.updateState2(2)
    assertThat(flowViewModel.mergedState.take(1).first()).isEqualTo(2)
}

方法3:若需合并最新状态而非发射序列

如果你的实际需求是获取两个StateFlow的最新组合值(而非每次单独更新都发射),merge并非合适选择,应该改用combine:

// 修改ViewModel中mergedState的定义
val mergedState = combine(state1, state2) { s1, s2 ->
    // 自定义合并逻辑,比如返回Pair或自定义数据类
    Pair(s1, s2)
}

此时每次state1或state2更新,都会发射两者的最新组合值,测试时能正确获取到合并后的状态。

内容的提问来源于stack exchange,提问作者Daniel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 01:23:28