I'm investigating kotlin coroutines related to Android after 1.0.0 release.
I found tons of examples of making scoped ViewModel (from arch components) with creating parent job and clearing it in onCleared
or scoped Activity with job creation in onCreate
and clearing in onDestroy
(same with onResume
and onPause
). In some examples I meet this code structure (taken from official docs):
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
Is this custom getter called all times, when we start a new coroutine from this scope? Isn't it bad? Maybe it would be better to keep single scope value instead of creating a new one every time?
[UPDATE]
I accept that solution, if we get rid of lateinit
job and create it isntantly, but what if I want to do something like this (what should I do? Is this solution looks like correct or not?):
class LifecycleCrScope: CoroutineScope, LifecycleObserver {
private var _job: Job? = null
override val coroutineContext: CoroutineContext
get() = job() + Dispatchers.Main
fun job() : Job {
return _job ?: createJob().also { _job = it }
}
fun createJob(): Job = Job() // or another implementation
@OnLifecycleEvent(ON_PAUSE)
fun pause() {
_job?.cancel()
_job = null
}
}
I think you could run into issues with thread-safety when you supply a Job
lazily like in your update because that code is evaluated from whatever thread happens to start a coroutine.
The initial example on the other hand makes sure that the Job is setup from the Main thread and that it happens before other threads can be started in a typical android activity.
You could achieve something similar to the initial example by creating the entire CoroutineContext
at the start of the scope. That also removes the need to compute the final context for each started coroutine. For example:
class LifecycleCrScope : CoroutineScope, LifecycleObserver {
private var _ctx: CoroutineContext? = null
override val coroutineContext: CoroutineContext
get() = _ctx!! // throws when not within scope
private fun startScope() {
_ctx = Dispatchers.Main + Job()
}
private fun endScope() {
_ctx!![Job]!!.cancel() // throws only when misused, e.g. no startScope()
_ctx = null
}
@OnLifecycleEvent(ON_RESUME) // <-.
fun resume() { // | Beware:
startScope() // | symmetric but no scope
} // | during onCreate,
// | onStart, onStop, ...
@OnLifecycleEvent(ON_PAUSE) // <-.
fun pause() {
endScope()
}
}
Alternatively, if you don't like it to throw
class LifecycleCrScope : CoroutineScope, LifecycleObserver {
// initially cancelled. Jobs started outside scope will not execute silently
private var _ctx: CoroutineContext = Dispatchers.Main + Job().apply { cancel() }
..
private fun endScope() {
_ctx[Job]!!.cancel() // should never throw
}
..