In a library of mine, I do `flow.flowOn(Default)` ...
# coroutines
z
In a library of mine, I do
flow.flowOn(Default)
where it makes sense; and life is good. Or so I would think, because further down the chain when I do
flow.map { .. }
and perform some heavy transformations on the data.. Ill eventually realize that Im doing those on the main-thread, since the backing scope is based on
Dispatchers.Main
. I need the results of the flow collection to be delivered on the main thread, but Id basically like all operations on it to be performed on a background one. I know that I can use withContext, specify another .flowOn, or even run the entire flow operation in a background scope - and then switch to main for the delivery.. but these seem reluctant or weird, and Id be repeating myself a lot. Is there any best practices or advice for situations like this?
n
Seems like you need
flow.map { ... }.flowOn(Dispatchers.Default).collect { ... }
? All the upstream will be executed on Dispatchers.Default, not just
map
.
z
Yes, exactly! As it turns out, I just had a build issue and the updated code wasnt deployed to my running app.. this works as expected. Thank you!
z
If you really want to communicate that any additional future operators should be on the same context, this makes it harder to accidentally add anything after the flowOn:
Copy code
withContext(Default) {
  flow
    .map {}
    .collect {
      withContext(Main) {
        …
      }
    }
}
z
Is there any downside to using
withContext(Main)
like that?
I was in a lengthy discussion about it yesterday, and effectively that would allow me to just specify the dispatcher, etc, in the scope itself, since all collection is forced to main
AI: Using
flow.flowOn(Default)
moves the upstream flow operations to the
Default
dispatcher, which means all emissions happen on
Default
before reaching downstream. In contrast, using
withContext(Main)
inside
collect
switches context per collected item, incurring context switch overhead for each emission.
Imagine if you will that all of this relates to a viewmodel fetching data in its scope, in reality its not a viewmodel, but thats the closest real world example I can think of
z
You can capture the dispatcher before the outer withContext if you just want to preserve it.
n
I cannot endorse the example with two withContexts. Someone suggested that you can spell it out this way if you want to stress that the upstream must be executed on Default.
Copy code
val flow = upstreamFlow.flowOn(Default)
withContext(Main) { flow.collect { ... } }
I personally think that it is every developer's responsibility to think where they want their code to execute, if they're adding an extra operator. I personally wouldn't recommend either option over the plain and simple
flow.map{ ... }.flowOn(Default).collect { ... }
. That's the standard syntax, that's exactly the use case it was created for, no need to complicate.