布尔流同步

Boolean flows sync

ViewModel 中的 StateFlow 字段很少 class。这是 add/edit 表单屏幕,其中每个 StateFlow 都是屏幕上每个可编辑字段的验证 属性。

我想用 StateFlow 属性 编写一些 class FormValidation 来验证整个表单的状态。该字段的值基于所有字段的验证状态值,当所有字段有效时发出 true,当任何字段无效时发出 false。

像这样:

class FormValidation(initValue: Boolean, vararg fieldIsValid: StateFlow<Boolean>) {
    
    private val _isValid = MutableStateFlow(initValue)
    val isValid: StateFlow<Boolean> = _isValid
    
    init {
        // todo: how to combine, subscribe and sync values of all fieldIsValid flows?
    }
    
}

我知道如何使用 LiveData<Boolean>MediatorLiveData,但我不明白如何使用流程。


基于@tenfour04

回答的解决方案
class BooleanFlowMediator(scope: CoroutineScope, initValue: Boolean, vararg flows: Flow<Boolean>) {
    val sync: StateFlow<Boolean> = combine(*flows) { values ->
        values.all { it }
    }.stateIn(scope, SharingStarted.Eagerly, initValue)
}

带有 StateFlow 和 ViewModel 的演示代码

class SyncViewModel : ViewModel() {

    companion object {
        private const val DEFAULT_VALUE: Boolean = false
    }

    private val values: List<List<Boolean>> = listOf(
        listOf(false, false, false),
        listOf(true, false, false),
        listOf(false, true, true),
        listOf(true, true, true)
    )

    private var index: Int = 0

    private val _flow1 = MutableStateFlow(DEFAULT_VALUE)
    val flow1: StateFlow<Boolean> = _flow1

    private val _flow2 = MutableStateFlow(DEFAULT_VALUE)
    val flow2: StateFlow<Boolean> = _flow2

    private val _flow3 = MutableStateFlow(DEFAULT_VALUE)
    val flow3: StateFlow<Boolean> = _flow3

    val mediator = BooleanFlowMediator(viewModelScope, DEFAULT_VALUE,
        flow1, flow2, flow3)

    fun generateValues() {
        val idx = (index + 1).mod(values.size).also { index = it }
        val row = values[idx]
        _flow1.value = row[0]
        _flow2.value = row[1]
        _flow3.value = row[2]
    }

}

我想你可以使用 combine 来做到这一点。它return是一个新的流,每次任何源流发出时都会发出,使用 lambda 中每个源流的最新值来确定其发出的值。

还有 combine 的重载,用于最多五个不同类型的输入流,一个用于任意数量的相同类型的流,这就是我们在这里想要的。

因为 Flow operators return 基本的冷流,但是如果你想要一个 StateFlow 以便你可以确定初始值,你需要使用 stateIn 将它转换回一个 StateFlow一个初始值。为此,您需要一个 CoroutineScope 来 运行 流入。我会留给您来确定要使用的最佳范围。也许它应该从拥有的 class 传入(如果 class 实例被 ViewModel“拥有”,则将 viewModelScope 传递给它)。如果您不使用传入范围,则必须在完成此 class 实例后手动取消范围,否则流程将泄漏。

我没有测试这段代码,但我认为这应该可以。

class FormValidation(initValue: Boolean, vararg fieldIsValid: StateFlow<Boolean>) {

    private val scope = MainScope()

    val isValid: StateFlow<Boolean> =
        combine(*fieldIsValid) { values -> values.all { it } }
            .stateIn(scope, SharingStarted.Eagerly, initValue)

}

但是,如果你不需要同步检查 Flow 的最新值(StateFlow.value),那么你根本不需要 StateFlow,你可以只暴露一个冷流.一旦冷流被收集,它就会开始收集它的源 StateFlow,因此它会立即根据所有源的当前值发出它的第一个值。

class FormValidation(initValue: Boolean, vararg fieldIsValid: StateFlow<Boolean>) {

    val isValid: Flow<Boolean> = when {
        fieldIsValid.isEmpty() -> flowOf(initValue) // ensure at least one value emitted
        else -> combine(*fieldIsValid) { values -> values.all { it } }
            .distinctUntilChanged()
    }

}