Hello guys, could you please tell me why exactly i...
# coroutines
q
Hello guys, could you please tell me why exactly in examples
Job
was added to the
coroutineContext
Copy code
override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job
?
s
So that you can get hold of the
Job
and cancel it when necessary
q
Could you please give me an example where use this job? If I write
launch
or
async
it will attach to the
scope
so then I will write
Copy code
override fun onDestroy() {
        super.onDestroy()
        coroutineContext.cancelChildren()
    }
s
In case you use a ViewModel, your
job
will be a
SupervisorJob
. In the ViewModels’ onClear, you’d call the
cancel
method on that
private val job = SupervisorJob()
Copy code
abstract class CoBaseViewModel(context: CoroutineContext) : ViewModel(), CoroutineScope {
    override val coroutineContext: CoroutineContext = SupervisorJob() + context

    override fun onCleared() {
        coroutineContext.cancel()
    }
}
(where
context
is usually just the
Dispatchers.Main
)
q
So with
+job
we can specify in which
Job
will be linked
launch
or
async
?
s
Yep, this will be the parent/top
job
from that point on
q
I see, and otherwise if I didn't specify some job it will be just
Job()
by default, right?
s
Yes, I think it’ll default to a plain Job().
q
got it, thank you!
I just was confused because in example was simple job
Copy code
private val job = Job()

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job
s
This will allow you to not only cancel, but join on it or anything else you’d like to do with the
job
. It’s a pattern 🙂
q
could you please give me some examples of usages. I understand why we need
SupervisorJob
to avoid stop parent coroutines by children crash. but why we need a job above. we stop all jobs by
coroutineContext.cancelChildren()
and if we want pause some specific job we save it instance from
launch
and then stop it. but what is best practise for this parent job?
s
If it is a top-level/top-parent Job, then
cancelChildren
suffices. But if your coroutine-scope and its context participates in structured concurrency, the calling context may need all its children to finish before it continues of finishes. That would mean
cancelChildren
is not enough, you’d need to call
cancel
to cancel your top-job.
q
oh, I see, thank you a lot for your patience and answers.
s
Of course. Glad to help!
👍 1
A great post by Roman about Structured Concurrency using coroutines: https://medium.com/@elizarov/structured-concurrency-722d765aa952
q
Thank you, will read!
@streetsofboston UPD: based on my tests. We must write
+job
there isn't default job. and by
coroutineContext.cancelChildren()
there isn't crash because of
?.
but also there isn't cancelation.
👍 1
g
get() = Dispatchers.Main + job
Instead of this idiom you also can consider ready to user
MainScope()
Copy code
class CoBaseViewModel : ViewModel(), CoroutineScope by MainScope()
it uses SupervisorJob by default and Main thread dispatcher One more thing: keeping reference on job or use
coroutineContext
to cancel job is not necessary, there is
CoroutineScope.cancel()
extension function, so for ViewModel you can just do this:
Copy code
override fun onCleared() {
    super.onCleared()
    cancel()
}
we stop all jobs by
coroutineContext.cancelChildren()
What is your use case for this? Fragments? Because for ViewModel it looks wrong, less error-prone to cancel Job, so if you have some async bug and will try to start coroutine after cancellation, this coroutine will be cancelled immediately, but with
coroutineContext()
you will get leaked coroutine instead
s
We tend to stay away from using MainScope and inject the Dispatcher instead to make it easier to test (inject Dispatchers.Main and for tests inject Dispatchers.Unconfined).
g
You can set Main dispatcher in tests, no need to inject it
You also can do something like this:
Copy code
class CoBaseViewModel(context: CoroutineContext) : ViewModel(), CoroutineScope by MainScope() + context
or even this
Copy code
class CoBaseViewModel(scope: CoroutineScope) : ViewModel(), CoroutineScope by scope
q
g
Check what?
This is not a global scope
I know this article %)
You can check “Special thanks” section
q
Checked, you are rock 🙂
g
😁
sorry, was just surprised find this mention %)
I do not say that passing context is bad, not at all, just want to point on some alternatives that imo a bit more idiomatic
And my suggestion is not about global scope
q
got it. thank you. main scope is the same that I wrote above but in much nice way
Copy code
MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
and in my example was
Copy code
private val job = SupervisorJob()

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job
g
yes, this is what I meant, also you can use delegation
Also, because Dispatchers.Main can be replaced in tests in most cases it’s not necessary to inject it if you don’t have other use cases than unit tests
s
We still inject it, not only the Main, but the IO (and Default) are injected as well. :-)
g
Usually I prefer to avoid IO injection, instead make all API that use IO or Default testable without dispatcher switch (this approach doesn’t work with Main, because you cannot use Main dispatcher in unit tests)
but again, nothing wrong with it, just a side note