Arjan van Wieringen
02/01/2023, 6:54 PMflow {
while (true) {
withFrameMillis { frameTime ->
emit(frameTime) // this will not work
}
}
}
withFrameMillis just has a very normal lamba. It must be possible to emit from that lambda right, since we're still in the flow-scope?Casey Brooks
02/01/2023, 6:56 PMwithFrameMillis
is called by some other mechanism that’s managing time, on that mechanism’s thread. The lambda can access the flow
scope, but that doesn’t mean it’s running in the same thread/coroutine context.
What you need instead is callbackFlow
so you can trySend
Arjan van Wieringen
02/01/2023, 6:58 PMephemient
02/01/2023, 6:59 PMflow
Casey Brooks
02/01/2023, 7:18 PMflow { emit() }
directly executes the lambda passed to flow.collect { }
. Of course things get much more complicated when it adds operators, .flowOn
, etc, but this is the general principle of Flows. For example:
val collector = FlowCollector<Unit> { }
flow<Unit> {
// `this === collector` is true
emit(Unit)
}.collect(collector)
But any callbacks that get registered within that flow will probably be running on a different context, since it’s a completely separate API or async mechanism that will eventually invoke the lambda. So the only way to guarantee values from one callback-based API match the coroutine context of the emitter/collector, is to pass the values through a Channel, which is designed to facilitate communication between different coroutine contexts. Conceptually, this is basically what a callbackFlow is doing (though again, the details are a bit more complicated):
val channel = Channel<Unit>(Channel.BUFFERED, BufferOverflow.SUSPEND)
withFrameMillis {
channel.trySend(Unit)
}
flow<Unit> {
for(value in channel) {
emit(value)
}
}
Arjan van Wieringen
02/02/2023, 11:06 AM