Can someone please tell why.. I have a shared fl...
# coroutines
s
Can someone please tell why.. I have a shared flow that emits a value when user taps a button:
Copy code
sharedFlow.collect { println("first") }

sharedFlow.collect { println("second") }
But actually 10 taps results in 10 prints of "first" message and never prints the "second" one. Does the shared flow accept only one collector, or could it's behavior widely depend on configuration and usage?
g
Because
collect
is a suspend function, which returns only when upstream flow is completed, which is never the case if you use StateFlow (it never completes)
if you want to just do collection in background and do not suspend during collection of this flow, you can use
launchIn(scopeForThisBackroundJob)
operator, which essentially the same as:
someScope.launch { flow.collect { block() } }
s
Thank you for your response but (if I got you right) I tried to do smth like this:
Copy code
MainScope().launch {
    sharedFlow.collect { println("value #1") }
    sharedFlow.collect { println("value #2") }
}
But still only first collector gets called..
k
If I understand correctly, those two collects are still in the same scope, so to be able to get those two collect called you would need to write:
Copy code
MainScope().launch {
    sharedFlow.collect { println("value #1") }
}
MainScope().launch {
    sharedFlow.collect { println("value #2") }
}
it would launch two coroutines without blocking each other
s
@KamilH so the
collect()
blocks the logical coroutine (not a physical thread), and we can just create several logical coroutines within single physical thread to get each
collect()
working within its own coroutine? I mean: "Physical" by operating system, not by CPU; and "logical" as just callbacks-driven system
k
Yes, exactly. Also this is what
launchIn
operator does under the hood. Creates a new coroutine and it blocks only “itself”
s
Thank you very much, you saved me several hours I still have an unknown behavior in another place but first will try to resolve it knowing the details above
👍 1
g
two collects are still in the same scope
To be precise, it’s not an issue of the same scope, it’s perfecly fine to have many coroutines on the same scope, problem that second collector not even start collecting, because first suspend and never return
s
So.. as I understand, the words above should look like:
Copy code
two collects are still in the same coroutine
g
it’s kinda tricky explanation %)
I think saying “coroutine is suspended” is just more correct and easier to understand when you know what suspended is
as Kamil mentioned, it’s like blocking for suspend function, but I wouldn’t use this word, it also creates confusion
it’s the same as you do not expect that this code will finish in 100 milliseconds: Thread.sleep(100) Thread.sleep(100) Just because second sleep is not started until first is finihsed, it’s just a list of sequential methods
same in this case, collect() designed to follow structured concurrence, it doesn’t start any background job (lifecycle of which you should handle), it just suspend until coroutine which it collecting is completed
s
"two collects are still in the same scope", this scope means os thread or another scope that I should learn? Currently I do understand the
collect()
inside any coroutine like a detached loop with its own separate mini life
g
inside any coroutine like a detached loop with its own separate mini life
It’s not detached, this is the point, it’s the same as any other loop
😣 1
this scope means os thread or another scope that I should learn
In general you shouldn’t worry about it, it really depends on many factors and abstracted from you by coroutines dispatcher and flow implementation
operator launchIn is what allows it to be detached, it creates one more coroutine on which this loop is running
Scope has nothing to do with threads itself, it’s just an abstraction, I recommend you to read official guide, it has pretty nice explanation of structured concurrency
s
But the coroutine converts to complex logic that contains switch/callback, right?
g
right
so in this case this coroutine waits “callback” for flow is completed before move to next step of this “switch”
And if you check StateFlow doc, it says: State flow never completes
s
And so it can't go to following commands within the coroutine as it's already busy 100% by
collect()
infinite (in theory) callbacks?
g
it’s not really busy, it’s just waits and do not proceed to the next call
s
Well, the callback firing does not cause this coroutine to go to the next command
g
Isn’t this the whole point of coroutines? 🙂
you do asyncronous operation waits it result
🍻 1
if coroutine wouldn’t wait result it wouldn’t be better for anyone
in your case you just should understand that collect does suspend (“block”) and do not proceed further until flow is completed
it’s the same as sequence { while(true) yeld(1) }.forEach { something() }
s
I agree, but you said:
it’s not really busy, it’s just waits and do not proceed to the next call
And I want to modify my answer so I don't understand it like thread blocking (busy), I do understand it like callbacks waiting
Smth like non-blocking waiting, smth like
declarative looking forward
g
yep, correct, this is the main advantage of coroutines, Threads.sleep doesn’t do any work, but it makes thread busy and prevents using this system resource (mostly RAM), coroutines do not have such problem, yes coroutine is suspended, but it’s light-weight object, not a system resource like thread
👍 1
s
And... sorry, how can I
launch()
coroutine within the current scope without having specify in hard manner
MainScope
,
DefaultScope
? Just "do this here in place"?
Or this scope is logical companion (not a thread), so it must be specified externally
g
how can I 
launch()
 coroutine within the current scope without having specify in hard manner
I don’t understand your question. You already can use launch directly if you are inside of another coroutine scope block, launch is an extension function for CoroutineScope
I see now you example above
MainScope().launch
and this is most probably wrong if you really use this code
if you are on Android, and you need main scope, use one provided by Activity/Fragment/ViewModel, do not create your own, otherwise it’s not different from usage of GlobalScope.launch I would recommend you to read official coroutines guide
s
Yep, it should be better then using your attention for a while And I'm working on multi-platform part of code between iOS and Android
g
anyway, in case of code related to UI you need some owner for the scope, which will handle lifecycle of this scope for you
👍 1
not sure how it handled on iOS side