我有LoginViewModel,其中包含邮箱和密码的合成状态支持字段.我想创建一个StateFlow来启用/禁用登录按钮.我用combinesnapshotFlowstateIn来表示.但问题是,只有initialvalue次被释放.当composition backed状态发生变化时,snapshotFlow看起来不会被触发.这是我期待的还是我错过了什么?

@Test
fun testStateFlow() = runTest {
    var emailState by mutableStateOf("")
    var passwordState by mutableStateOf("")
    
    val allInputValid: StateFlow<Boolean> = combine(
        snapshotFlow { emailState },
        snapshotFlow { passwordState }
    ) { emailValue, passwordValue ->
        emailValue.isNotEmpty() && passwordValue.isNotEmpty()
    }.stateIn(
        CoroutineScope(UnconfinedTestDispatcher(testScheduler)),
        SharingStarted.WhileSubscribed(5000),
        false
    )

    val test = mutableListOf<String>()
    val job = launch {
        allInputValid.collect {
            test.add("Value emitted: $it")
            println(it)
        }
    }
    
    advanceUntilIdle()
    emailState = "test_email"
    passwordState = "test_password"

    advanceUntilIdle()
    println(test)
    
    job.cancel()
}

推荐答案

当您设置为emailState = "test_email"时,您希望snapshotFlow检测到更改的状态.但是,只有当快照系统可以观察到更改时,这才能起作用.在您的生产代码中,由于更改源自在这种快照上下文中运行的合成函数而工作.在单元测试中是孤立的,没有任何东西可以检测到更改,因此snapshotFlow永远不会收到通知.

你总是可以创建这样的上下文ad—hoc(not recommended, see below):

Snapshot.withMutableSnapshot {
    emailState = "test_email"
    passwordState = "test_password"
}

这也需要在视图模型中完成:由于视图模型可以在compose上下文之外使用(如在单元测试中),所以在视图模型更改MutableState的任何地方,它都应该用Snapshot.withMutableSnapshot包装.

这是一个繁琐的过程,而且容易出错,因此推荐的处理方法是从视图模型中删除所有MutableState.

使用MutableStateFlow(*).它可以类似于MutableState的使用:通过设置它的value属性来更改它的值,并且由于它已经是一个流,所以可以很容易地与其他流组合.

你的问题的测试看起来像这样,完全绕过了有问题的MutableState:

@Test
fun testStateFlow() = runTest {
    val emailState = MutableStateFlow("")
    val passwordState = MutableStateFlow("")

    val allInputValid: StateFlow<Boolean> = combine(
        emailState,
        passwordState,
    ) { emailValue, passwordValue ->
        emailValue.isNotEmpty() && passwordValue.isNotEmpty()
    }.stateIn(
        CoroutineScope(UnconfinedTestDispatcher(testScheduler)),
        SharingStarted.WhileSubscribed(5000),
        false,
    )

    val test = mutableListOf<String>()
    val job = launch {
        allInputValid.collect {
            test.add("Value emitted: $it")
            println(it)
        }
    }

    advanceUntilIdle()
    emailState.value = "test_email"
    passwordState.value = "test_password"
    advanceUntilIdle()
    println(test)

    job.cancel()
}

(*):虽然名称MutableStateMutableStateFlow看起来非常相似,但除了语义上的一些不同之处.MutableState是Google Compose框架的一种类型,MutableStateFlow是Flow框架的一种类型,它是Kotlin语言本身的一个组成部分.

Kotlin相关问答推荐

UByte范围. Min_UTE.. UByte.MAX_UTE不符合预期

Android前台服务 list —Altbeacon

如何在使用Kotlin Coroutines时检测和记录何时出现背压

在Kotlin中将String转换为T

使用另一个对象的列表创建对象

编译后的JavaFX应用程序立即以静默方式崩溃

我可以在kotlin/java.util.scanner中跳过分隔符而不重复吗?

在Kotlin lambda的参数中如何指定函数类型?

kotlin 模式匹配如何像 scala 一样工作

判断 Kotlin 变量是否为函数

如何为 Kotlin 中的每个循环设置以避免越界异常

Kotlin SAM/功能接口抛出 AbstractMethodError

为空数组添加值

类型是什么意

如何使用 gradle 脚本 Kotlin 构建文件构建可运行的 ShadowJar?

将协同路由调用放在存储库或ViewModel中哪个更好?

Kotlin中的下划线名称是为什么保留的?

如何在Kotlin中获得KType?

在 Kotlin 中创建 Spinner 时,如何在 Fragment 中的旋转屏幕上修复指定为非空的参数为空?

Kotlin:在何时使用枚举