natario1
08/03/2021, 1:44 PMFlow<State>
that represents some state and a Channel<Event>
? Regular combine(...) does not work well, because the event should only be processed once (against the most recent state). I currently achieve this with a pretty complex channelFlow { } with two coroutines and saving stuff in local `var`s, but I wonder if there's a smarter way.Dominaezzz
08/03/2021, 3:06 PMwithLatestFrom
. There's an open issue on GitHub.natario1
08/03/2021, 3:16 PMother
emits, the transform is not invoked https://github.com/Kotlin/kotlinx.coroutines/issues/1498 while I'd like to react on bothDominaezzz
08/03/2021, 4:52 PMzip
then?Nick Allen
08/03/2021, 4:56 PMstateIn
can give you a StateFlow
so you can just grab the value.natario1
08/03/2021, 6:54 PMtransform: (State, Event?) -> SomethingElse
. Solutions based on combine have two issues:
• transform won't be called until the first Event
• transform will be passed the latest Event
multiple times, if state emits fast. this is hard to workaround
I ended up with this, not sure if best option but seems to work:
// consume T2, never send it twice
fun <T1, T2: Any, R> Flow<T1>.combineConsuming(
flow: Flow<T2>,
block: suspend (T1, T2?) -> R
) : Flow<R> {
val flow1 = this
val flow2 = flow
return channelFlow {
val latest1 = MutableSharedFlow<T1>(replay = 1)
val job2 = flow2.onEach { t2 ->
val t1 = latest1.first()
send(block(t1, t2))
}.launchIn(this)
flow1.onEach { t1 ->
send(block(t1, null))
}.onCompletion {
job2.cancel()
}.launchIn(this)
}
}
Dominaezzz
08/03/2021, 7:47 PMtransform
to be called?natario1
08/03/2021, 7:50 PMb: B
should be passed to transform exactly once.Dominaezzz
08/03/2021, 7:52 PMnatario1
08/03/2021, 7:55 PMDominaezzz
08/03/2021, 8:00 PM@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
fun <A, B : Any, R> Flow<A>.doThing(
flow: Flow<B?>,
transform: suspend (A, B?) -> R
): Flow<R> {
return flow {
coroutineScope {
val aChannel = this@doThing.produceIn(this)
val bChannel = flow.produceIn(this)
var lastA: A = aChannel.receive()
emit(transform(lastA, bChannel.tryReceive().getOrNull()))
whileSelect {
aChannel.onReceive { a ->
emit(transform(a, null))
lastA = a
true
}
bChannel.onReceive { b ->
emit(transform(lastA, b))
true
}
}
}
}
}
Nick Allen
08/03/2021, 10:54 PMlatest1
is never populated, first()
just suspends). Your approach works just fine as a way to implement your own operator. Sometimes, building your own can be easier to read than a rube goldberg machine using existing operators.natario1
08/04/2021, 10:34 AM