我需要处理流收集中的当前值和先前值,因此我需要一些具有类似功能的运算符:
----A----------B-------C-----|--->
---(null+A)---(A+B)---(B+C)--|--->
一个想法是这样的:
fun <T: Any> Flow<T>.withPrevious(): Flow<Pair<T?, T>> = flow {
var prev: T? = null
[email protected] {
emit(prev to it)
prev = it
}
}
但是这样就无法控制执行第一个流程的上下文。有更灵活的解决方案吗?
有一个运算符可以让这变得非常简单:
runningFold
文档有一个关于如何使用它来收集流的每次发射的示例;这可以很容易地适应我们的需求
data class History<T>(val previous: T?, val current: T)
// emits null, History(null,1), History(1,2)...
fun <T> Flow<T>.runningHistory(): Flow<History<T>?> =
runningFold(
initial = null as (History<T>?),
operation = { accumulator, new -> History(accumulator?.current, new) }
)
// doesn't emit until first value History(null,1), History(1,2)...
fun <T> Flow<T>.runningHistoryAlternative(): Flow<History<T>> =
runningHistory().filterNotNull()
您可能需要调整可空性以适合您的用例
Flow
是连续的,因此您可以使用变量来存储先前的值:
coroutineScope.launch {
var prevValue = null
flow.collect { newValue ->
// use prevValue and newValue here
...
// update prevValue
prevValue = newValue
}
}
我认为您可以使用
runningFold
轻松完成此操作,因为使用它,您可以使用折叠操作将一个流程转换为另一个流程。这是因为 runningFold
确实将所有内容折叠成一个值,如 fold
。相反,它将所有步骤保留为所有突变的历史记录。您唯一需要关心的是每个返回值必须是一对,第一个值必须为 null,并且第一个对的左侧值必须允许为 null。允许 null 作为累加器也意味着必须过滤掉该值。
粗略地说,您需要遵循这样的示例:
val initial: Pair<String?, String>? = null
val allPairs = flow {
emit("Oyster")
emit("Clam")
emit("Crab")
}.runningFold(initial) { lastPair, next ->
lastPair?.run {
val (_, last) = this
last to next
} ?: (null to next)
}.filterNotNull()
allPairs.collect {
println(it)
}
这样您就不需要创建运营商类别类型,因为您的运营商类别已经是
Pair
类别。
您可以使用
fold
测试差异,如下所示:
val initial: Pair<String?, String>? = null
val pair = flow {
emit("Oyster")
emit("Clam")
emit("Crab")
}.fold(initial) { lastPair, next ->
lastPair?.run {
val (_, last) = this
last to next
} ?: (null to next)
}
println(pair)
在此处查看有关折叠及其不同形式的更多信息: