Is there a reason the Compose `CoroutineScope` doe...
# compose
j
Is there a reason the Compose
CoroutineScope
doesn't use an immediate
CoroutineDispatcher
by default, as is the case for Android's
ViewModel
and
Lifecycle
`CoroutineScope`s? Having an immediate dispatcher allows updating state synchronously, like for
TextField
using a
StateFlow
. See this thread for more examples and discussion. CC @Arkadii Ivanov
4
e
Did you file an issue on issuetracker, or are you waiting for input here first?
j
I created this issue in Compose Multiplatform, mostly because of the lack of access to
Dispatchers.Main.immediate
without the additional
kotlinx-coroutines-swing
dependency, which isn't documented well. I'll create an issue in Google's tracker as well.
I created this issue on Google's tracker. I am curious if there's a reason behind why a
Dispatchers.Main.immediate
is used in some cases. But
Dispatchers.Main
is used in others.
👍 1
@eygraber when you replace the
CoroutineContext
in the remembered
CoroutineScope
, is this context then used for all
collectAsState
calls for that composable? Does that context get passed down to child composables and their `CoroutineScope`s as well? Or does the context need to be provided in all of these places explicitly?
e
No, it's just for that
scope
which is used internally. If I wanted the same behavior elsewhere I'd have to do the same thing. It hasn't come up yet, but if it did I'd probably extract it to a function and call it
rememberImmediateCoroutineScope
or something like that. Alternatively I could hoist it out of the composition and make it a protected property of
VicePortal
, but it would still have to be explicitly used.
z
Immediate dispatchers are generally bad and make coroutine code hard to reason about. There’s tons of info out there about this. We used one for the compose test framework and have been regretting it ever since.
e
@Zach Klippenstein (he/him) [MOD] is the solution to remove any form of async requiring synchronous handling for state updates, along the lines of how BTF2 is handling it?
z
yes
💥 2
j
There are times when coroutines are expected to behave asynchronously, and using an immediate or unconfined dispatcher in those situations could have unexpected results. But there are also cases where you might be using a coroutine mechanism, like
StateFlow
or
Channel
, but you actually need the emissions and collections to be synchronous. It's equally hard to reason with the current subtle behavior of
TextField
using
StateFlow
or a `Channel` on a non-immediate dispatcher. So I wouldn't say immediate dispatchers are always bad from this perspective. If it's the right tool, it makes sense. Of course if there are API changes that address those needs, they could be used instead, like BTF2. But there are other use cases that don't have a solution yet that doesn't require an immediate dispatcher.
Would you mind giving some examples where a non-immediate dispatcher is the preferred behavior for using a composable's
CoroutineScope
? I'm not finding a ton of info about immediate dispatchers being inherently bad from my searches, just these few (1, 2, 3). The last one mostly describes misunderstanding the behavior. The second one is the original PR, but the disclaimer comment Roman added has since been removed from the docs, which now describe how immediate is safe from stack overflows instead. Also compare
isDispatchNeeded
docs then and now.
s
I agree that immediate dispatchers are not exactly the easiest to reason about. The current behavior might be confusing for flow collection, but with immediate dispatcher other coroutine builders also start executing until the first suspension point, which does not match default behavior. I don't think changing the default dispatcher at this point is possible, there are sequencing expectations for things like LaunchedEffect, which are going to change with immediate dispatcher. You can create your own primitives with the correct dispatcher if needed though.
a
It would be good to use the immediate dispatcher in collectAsState though. It shouldn't hurt, I guess.
1
Or maybe add a Local for this case, so that we could configure it once and for every collectAsState.