https://kotlinlang.org logo
Title
q

quver

03/19/2019, 8:34 PM
Hello guys, could you please tell me why exactly in examples
Job
was added to the
coroutineContext
override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job
?
s

streetsofboston

03/19/2019, 8:40 PM
So that you can get hold of the
Job
and cancel it when necessary
q

quver

03/19/2019, 8:44 PM
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
override fun onDestroy() {
        super.onDestroy()
        coroutineContext.cancelChildren()
    }
s

streetsofboston

03/19/2019, 8:45 PM
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()
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

quver

03/19/2019, 8:54 PM
So with
+job
we can specify in which
Job
will be linked
launch
or
async
?
s

streetsofboston

03/19/2019, 8:56 PM
Yep, this will be the parent/top
job
from that point on
q

quver

03/19/2019, 9:00 PM
I see, and otherwise if I didn't specify some job it will be just
Job()
by default, right?
s

streetsofboston

03/19/2019, 9:04 PM
Yes, I think it’ll default to a plain Job().
q

quver

03/19/2019, 9:05 PM
got it, thank you!
I just was confused because in example was simple job
private val job = Job()

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job
s

streetsofboston

03/19/2019, 9:10 PM
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

quver

03/19/2019, 9:23 PM
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

streetsofboston

03/19/2019, 9:29 PM
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

quver

03/19/2019, 9:32 PM
oh, I see, thank you a lot for your patience and answers.
s

streetsofboston

03/19/2019, 9:33 PM
Of course. Glad to help!
👍 1
A great post by Roman about Structured Concurrency using coroutines: https://medium.com/@elizarov/structured-concurrency-722d765aa952
q

quver

03/19/2019, 9:36 PM
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

gildor

03/20/2019, 12:53 AM
get() = Dispatchers.Main + job
Instead of this idiom you also can consider ready to user
MainScope()
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:
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

streetsofboston

03/20/2019, 1:20 AM
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

gildor

03/20/2019, 1:25 AM
You can set Main dispatcher in tests, no need to inject it
You also can do something like this:
class CoBaseViewModel(context: CoroutineContext) : ViewModel(), CoroutineScope by MainScope() + context
or even this
class CoBaseViewModel(scope: CoroutineScope) : ViewModel(), CoroutineScope by scope
q

quver

03/20/2019, 9:03 AM
g

gildor

03/20/2019, 9:03 AM
Check what?
This is not a global scope
I know this article %)
You can check “Special thanks” section
q

quver

03/20/2019, 9:04 AM
Checked, you are rock 🙂
g

gildor

03/20/2019, 9:04 AM
😁
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

quver

03/20/2019, 9:07 AM
got it. thank you. main scope is the same that I wrote above but in much nice way
MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
and in my example was
private val job = SupervisorJob()

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job
g

gildor

03/20/2019, 9:14 AM
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

streetsofboston

03/20/2019, 9:51 AM
We still inject it, not only the Main, but the IO (and Default) are injected as well. :-)
g

gildor

03/20/2019, 9:54 AM
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