https://kotlinlang.org logo
Title
a

Arjan van Wieringen

02/01/2023, 6:54 PM
What am I missing here?
flow {
    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?
c

Casey Brooks

02/01/2023, 6:56 PM
Think of it in terms of where the code is executing. the lambda of
withFrameMillis
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
a

Arjan van Wieringen

02/01/2023, 6:58 PM
Yes, that one
@Casey Brooks I think that makes sense. I'll try your suggestion
e

ephemient

02/01/2023, 6:59 PM
then it is as Casey says
in practice, on Android, the lambda is being executed on the UI thread from the Choreographer frame callback, not from within the
flow
(e.g. it's like switching dispatchers, except that the other context isn't really a "dispatcher" in the kotlinx.coroutines sense, but it's still different than the flow dispatcher)
c

Casey Brooks

02/01/2023, 7:18 PM
In general, Flows run on the thread/coroutine context that collects them, because under-the-hood a call to
flow { 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)
    }
}
a

Arjan van Wieringen

02/02/2023, 11:06 AM
Amazing thanks!