val wordFlow = MutableStateFlow("Hi")
val pointFlow = MutableStateFlow(5)
val stateString = wordFlow.combine(pointFlow) { word, points ->
"$word is worth $points points"
}.stateIn(viewModelScope, SharingStarted.Eagerly, "Default is worth 0 points")
上述解决方案使用stateIn()和GlobalScope,策略为Eagerly,这意味着***这些StateFlows一旦创建就永远不会停止观察,这可能导致问题***。 我已经提到了details in this blog。相反,创建一个单独的类来派生一个新的StateFlow:
private class TransformedStateFlow<T>(
private val getValue: () -> T,
private val flow: Flow<T>
) : StateFlow<T> {
override val replayCache: List<T> get() = listOf(value)
override val value: T get() = getValue()
override suspend fun collect(collector: FlowCollector<T>): Nothing =
coroutineScope { flow.stateIn(this).collect(collector) }
}
/**
* Returns [StateFlow] from [flow] having initial value from calculation of [getValue]
*/
fun <T> stateFlow(
getValue: () -> T,
flow: Flow<T>
): StateFlow<T> = TransformedStateFlow(getValue, flow)
/**
* Combines all [stateFlows] and transforms them into another [StateFlow] with [transform]
*/
inline fun <reified T, R> combineStates(
vararg stateFlows: StateFlow<T>,
crossinline transform: (Array<T>) -> R
): StateFlow<R> = stateFlow(
getValue = { transform(stateFlows.map { it.value }.toTypedArray()) },
flow = combine(*stateFlows) { transform(it) }
)
/**
* Variant of [combineStates] for combining 3 state flows
*/
inline fun <reified T1, reified T2, reified T3, R> combineStates(
flow1: StateFlow<T1>,
flow2: StateFlow<T2>,
flow3: StateFlow<T3>,
crossinline transform: (T1, T2, T3) -> R
) = combineStates(flow1, flow2, flow3) { (t1, t2, t3) ->
transform(
t1 as T1,
t2 as T2,
t3 as T3
)
}
// Other variants for combining N StateFlows
在此之后,您可以在您的用例中实现它。例如:
private val isLoading = MutableStateFlow(false)
private val loggedInUser = MutableStateFlow<User?>(null)
private val error = MutableStateFlow<String?>(null)
// Combining these states to form a LoginState
val state: StateFlow<LoginState> = combineStates(isLoading, loggedInUser, error) { loading, user, errorMessage ->
LoginState(loading, user, errorMessage)
}
data class A(val a: String)
data class B(val b: Int)
private val test1 = MutableStateFlow(A("a"))
private val test2 = MutableStateFlow(B(2))
@Suppress("CHANGING_ARGUMENTS_EXECUTION_ORDER_FOR_NAMED_VARARGS")
private val _isValidForm = combineStateFlow(
flows = arrayOf(test1, test2),
scope = viewModelScope
) { combinedFlows: Array<Any> ->
combinedFlows.map {
val doSomething = when (it) {
is A -> true
is B -> false
else -> false
}
}
}
fun <T1, T2, R> combine(flow: StateFlow<T1>, flow2: StateFlow<T2>, initialValue: R, transform: suspend (a: T1, b: T2) -> R): StateFlow<R> {
return object : StateFlow<R> {
private val mutex = Mutex()
override var value: R = initialValue
override val replayCache: List<R> get() = listOf(value)
override suspend fun collect(collector: FlowCollector<R>): Nothing {
combine(flow, flow2, transform)
.onStart { emit(initialValue) }
.collect {
mutex.withLock {
value = it
collector.emit(it)
}
}
error("This exception is needed to 'return' Nothing. It won't be thrown (collection of StateFlow will never end)")
}
}
}
使用方法:
val letters = MutableStateFlow("A")
val numbers = MutableStateFlow(0)
val lettersAndNumbers: StateFlow<String> = combine(letters, numbers, "initial") { a, b -> a + b }
7条答案
按热度按时间tktrz96b1#
您可以使用
combine
运算符,然后对由此产生的Flow
使用stateIn
函数。从kotlinx协程存储库中的
stateIn
documentation:stateIn
函数“将 coldFlow
转换为在给定协程作用域中启动的 hotStateFlow
,并与多个下游订阅者共享上游流的单个运行示例中最近发出的值。”它的签名,截至写这篇文章的时候,是:
因此,您应该能够对
Flow
进行所需的任何转换,包括组合它们,然后最终使用stateIn
将它们转换回StateFlow
。它可能看起来像这样(也许创建一个Scrabble游戏点数计算器):
stateString
的类型为StateFlow<String>
,而您已经成功地将另外两个StateFlows
合并为一个StateFlow
。3df52oht2#
到目前为止,我创建了函数:
2exbekwf3#
上述解决方案使用
stateIn()
和GlobalScope
,策略为Eagerly
,这意味着***这些StateFlows一旦创建就永远不会停止观察,这可能导致问题***。我已经提到了details in this blog。相反,创建一个单独的类来派生一个新的
StateFlow
:在此之后,您可以在您的用例中实现它。例如:
这种方法比其他提到的方法更安全,因为它只会在实际收集
StateFlow
更新时(在消费者的Coroutine范围内)侦听它xuo3flqw4#
与@Nikola Despotoski类似的解决方案,但采用扩展函数的形式
xxslljrj5#
合并n个状态流
使用方法:
Gist
bsxbgnwa6#
如果你有一个初始值,那么合并两个
StateFlows
就不需要CoroutineScope
:使用方法:
您可以使用此kotlin playground demo检查使用情况
suzh9iv87#
使用
combine
操作符,它需要两个流和一个转换函数来合并来自两个流的结果。