Open
Description
Use case
Combine several flows in a more convenient (and possibly more efficient) way.
We have flows which combine dozens of flows. Existing combine
allows to combine at most 5 flows in a type safe way, or to combine an array of flows, but it only works if flows have the same type.
The Shape of the API
fun <T> combine(transform: suspend CombineScope.() -> T): Flow<T> = TODO()
interface CombineScope { // : CoroutineScope ?
/**
* Starts collection of [this] flow if not yet started.
* Suspends until [this] flow emits the first value.
* @return the latest emitted value
*/
suspend fun <T> Flow<T>.latestValue(): T
/**
* Starts collection of [this] flow if not yet started.
* @return the latest emitted value or [initialValue] if [this] flow did not emit anything yet
*/
suspend fun <T> Flow<T>.latestValue(initialValue: T): T
}
fun usage(ints: Flow<Int>, floats: Flow<Float>): Flow<String> = combine {
val i = ints.latestValue()
if (i < 0) {
return@combine "x"
}
else {
val f = floats.latestValue(initialValue = 2f)
return@combine f.toString() + i.toString()
}
}
Pros:
- No need to choose type unsafe varargs overload.
- Allows to have the variable declaration close to the flow:
val i = ints.latestValue()
. - Allows to avoid collecting unnecessary flows: if the first value from
ints
is-1
, thenfloats
will not be even discovered, and will not be collected untilints
emits a value> 0
. This can be considered a con (see con №1). - Allows to skip running
transform
unnecessarily: if bothints
andfloats
are discovered and are being collected, and the latest emitted value fromints
was< 0
, then all emitted values fromfloats
can be ignored untilints
emit a new value, because "x" will be the result oftransform
regardless off
value.
Cons:
- In case of
combine {
val i = ints.latestValue()
val f = floats.latestValue()
}
floats
will be started collecting after ints
emits a value (see pro №3).
Prior Art