布尔流同步
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()
}
}
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()
}
}