I have a problem with `Flow.collectAsState()` wher...
# compose
a
I have a problem with
Flow.collectAsState()
where collection of the flow starts one frame after initial composition. Usually my
Flow
emits its first value immediately upon subscription, and in that case I don’t want to see
initialValue
. But because collect starts only after first frame, there’s visible fast “blink” when you first open a screen. As I understand it’s because
LaunchedEffect
executes it’s lambda in
AndroidUiDispatcher
, and it dispatches `LaunchedEffect`’s job only after first frame has been drawn. I found a solution, where if I launch a coroutine that collects the
Flow
with
start = CoroutineStart.UNDISPATCHED
, value is produced immediately after first composition (so I can read it inside
SubcomposeLayout
of some sort, and screen does not “blink”). Is there any pitfalls of doing so?
a
If you're launching a coroutine directly in composition as opposed to using LaunchedEffect then yes, there are a great many pitfalls to doing so. 🙂
Using a StateFlow that has the initial value already available or otherwise providing the initial value to collectAsState would be the quickest fix here
a
No, I mean if I create let’s say LaunchedEffectImmediate, that is otherwise the same as LaunchedEffect, but uses CoroutineStart.UNDISPATCHED to start coroutine
a
How would you create that?
a
Copying LaunchedEffectImpl sources
a
It uses internal API that isn't exposed
a
All I found is it uses currentComposer.applyCoroutineContext, which can probably be replaced with rememberCoroutineScope()
a
won't be quite the same but might be close enough for your purposes. Then you're left with another practical issue: the RememberObserver that would launch your coroutine is run after composition but before measure/layout/draw in the same frame.
Your initial value emitted by your flow would be available for reading in measure/layout/draw contexts, but you already composed without that value.
a
Yeah, but if I use SubcomposeLayout or LazyList, then it’s content will be composed at measure stage
a
only children in those, not the same composition where you're at
and even then there are recomposition cases where that doesn't actually hold, and subcompositions recompose as part of the normal composition phase instead of deferring to measure/layout if the measure/layout input parameters didn't change
you're deep in situational edge cases here that are likely to be pretty brittle
what you could do instead, and I'd put a giant warning sign on this
is to launch the coroutine in a
remember {}
block directly and then cancel it in both
onForgotten
and
onAbandoned
in the RememberObserver
at this point it's important that your flow be aware that doing this undispatched could very much be on a very different thread in the future for that run to initial suspend
and it's important that this flow not have side effects that would otherwise be troublesome if cancelled as part of onAbandoned in a, "this never actually happened" scenario
in short, we considered making all of this default behavior of collectAsState and decided against it as not being worth it. 🙂
a
hmm, yeah, I think this will probably do the job. Otherwise I would need to do Flow.shareIn() in a scope of Fragment/ViewModel. This Flow is combination of Rx’s BehaviorSubject and StateFlows with a sprinkle of map and combine operators, and without side effects. About threads - are you talking about multithreaded composer that will be released in the future?
a
yes
a
ok, thanks for clarifying all of this 🙂
a
sure, happy to hand you a loaded foot-gun here 😛
joking aside, experiment with it; I suspect you'll encounter the reasons we backed away from it and end up doing the same in the end
but if not and it works for you, ¯\_(ツ)_/¯
with how you described the data source, the whole setup might get a lot cleaner with snapshot state and simple functions that read that state/return values computed from it
depends on whether you have any cold sources in there or not, but even if so, running a sort of
runUpdates()
function in a
LaunchedEffect
can be highly effective for these cases