dan.the.man
09/09/2020, 2:31 PMFragment {
lifecycleScope.launch { vm.attachListeners }
}
VM {
val locationListener:ReceiveChannel<Location>
val navListener:ReceiveChannel<NavEvent>
suspend fun attachListeners(){
locationListener.consumeEach{//Do stuff}
navListener.consumeEach{//do stuff}
}
With the code above, if this was RxJava, it would behave correctly. With Coroutines the first .consumeEach
is blocking it appears, never executing the second one.
I can change it to
suspend fun attachListeners(){
coroutineScope{
launch{
locationListener.consumeEach{//Do stuff}
}
launch {
navListener.consumeEach{//do stuff}
}
}
}
And that will work. The goal is to make it automatically cancel with the lifecycleScope, I'm wondering is there a better way of doing this? And why does .consumeEach block?Casey Brooks
09/09/2020, 2:41 PMconsumeEach
is a suspending function, which means it will suspend at that line until the channel is closed and it has consumed everything in the channel. By wrapping each consumeEach { }
in launch
, they are each running in parallel, which is what you want. But now you have a different problem: the coroutineScope { }
block will suspend until both channels complete, and so will attachListeners
. This probably isn’t what you intended, you’re wanting attachListeners
to start off the background jobs for consuming the channels and then return once they’re started (but not finished). For this case, to follow Structured Concurrency, you probably want attachListeners
to have a receiver of CoroutineScope
without the suspend
modifier, so that the launch { }
blocks are tied into the scope of the caller (the Fragment’s lifecycleScope)
fun CoroutineScope.attachListeners(){
launch{
locationListener.consumeEach{//Do stuff}
}
launch {
navListener.consumeEach{//do stuff}
}
}
dan.the.man
09/09/2020, 2:52 PMFragment {
lifecycleScope.launch { vm.handleAction()}
}
VM {
val locationListener:ReceiveChannel<Location>
val navListener:ReceiveChannel<NavEvent>
suspend fun handleAction(action:Action){
attachListeners()
}
suspend fun attachListeners(){
locationListener.consumeEach{//Do stuff}
navListener.consumeEach{//do stuff}
}
My full code looks something like this, so changing it to a CoroutineScope.attach
is then giving me compilation error,
I can then wrap it in coroutineScope { attachListeners}
But that feels like it's the same problem I was just trying to solve by moving it to an extensino of Coroutine scope?coroutineScope {}
would launch another coroutine scope within the parent scope that it was called ffrom, I could have the incorrect understanding on that thoughCasey Brooks
09/09/2020, 3:11 PMsuspend
or have a CoroutineScope
receiver, and where to put the coroutineScope { }
block. But the overall design of your coroutines will be easier to understand (and follow other libraries) with the general rule that suspend
functions are designed for things that need to run in sequence (and don’t need a coroutine builder inside them), and CoroutineScope
receiver functions for running things in parallel (which needs a separate coroutine builder for each parallel task).dan.the.man
09/09/2020, 3:15 PMCasey Brooks
09/09/2020, 3:16 PMcoroutineScope
block inside a suspend
function with launch
or async
inside that, so the two API calls can be in parallel, but to the outside world it’s just a sequential task that returns a single value (albeit, and aggregate value) (see examples here https://medium.com/@elizarov/structured-concurrency-722d765aa952#0be1, also just a good read on this topic)dan.the.man
09/09/2020, 3:17 PMbdawg.io
09/09/2020, 11:58 PM