0

我在 ViewModel 类中有几个 StateFlow 字段。它是添加/编辑表单屏幕,其中每个 StateFlow 都是屏幕上每个可编辑字段的验证属性。

我想写一些带有 StateFlow 属性的 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]
    }

}
4

1 回答 1

1

我认为您可以使用combine. 它返回一个新的 Flow,每次任何源 Flow 发出时都会发出,使用 lambda 中每个的最新值来确定其发出的值。

最多可以重载combine5 个不同类型的输入流,一个用于任意数量的相同类型的流,这就是我们想要的。

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

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

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,您可以只暴露一个冷 Flow。收集冷流的那一刻,它将开始收集其源 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()
    }

}
于 2021-09-13T19:03:21.667 回答