We should provide factory method fun MainScope() = CoroutineScope(Dispatchers.Main + SupervisorJob()).
It fixes multiple issues (especially with the #828):
async behaviour when it is launched from UI componentLet me add some more rationale here and additional suggestions. With this change an introduction to programming with coroutines for Android developers (see https://codelabs.developers.google.com/codelabs/kotlin-coroutines/index.html#4) could give the following example on "Controlling the UI with coroutines":
class MainViewModel(private val repository: TitleRepository) : ViewModel() {
// ...
private val uiScope = MainScope()
override fun onCleared() {
super.onCleared()
uiScope.cancelCoroutineScope()
}
}
This gives the following benefits:
It avoids introducing the concepts of Job and CoroutineContext to novice developers.
It internally uses SupervisorJob() instead of Job(), which generally has less surprising behavior here as opposed to Job(), since a crash of a single coroutine would only lead to a crash of this particular coroutine. It does not matter by default (where that would usually crash an application), but this plays nicely with custom coroutine exception handlers for advanced users, which could use them like this:
private val uiScope = MainScope() + CoroutineExceptionHandler { ctx, ex ->
/* do something on error -- for example, log but don't crash. */
}
It would not have been this easy with Job(), because even if you handle exception, the Job() is cancelled after the crash and all future coroutines fail to start.
Note the name of cancelCoroutineScope() extension here. It is long and verbose for a reason, because a short name like cancel() could get quickly confusing in cases where CoroutineScope is serving as this implicit receiver scope:
launch { // note: this: CoroutineScope
// ... other code
cancel() // what are we cancelling here?
}
This naming also plays nicely with the following pattern of using MainScope() by interface delegation:
class MainViewModel(
private val repository: TitleRepository
) : ViewModel(), CoroutineScope by MainScope() {
// ...
override fun onCleared() {
super.onCleared()
cancelCoroutineScope()
}
}
Most helpful comment
Let me add some more rationale here and additional suggestions. With this change an introduction to programming with coroutines for Android developers (see https://codelabs.developers.google.com/codelabs/kotlin-coroutines/index.html#4) could give the following example on "Controlling the UI with coroutines":
This gives the following benefits:
It avoids introducing the concepts of
JobandCoroutineContextto novice developers.It internally uses
SupervisorJob()instead ofJob(), which generally has less surprising behavior here as opposed toJob(), since a crash of a single coroutine would only lead to a crash of this particular coroutine. It does not matter by default (where that would usually crash an application), but this plays nicely with custom coroutine exception handlers for advanced users, which could use them like this:It would not have been this easy with
Job(), because even if you handle exception, theJob()is cancelled after the crash and all future coroutines fail to start.Note the name of
cancelCoroutineScope()extension here. It is long and verbose for a reason, because a short name likecancel()could get quickly confusing in cases whereCoroutineScopeis serving asthisimplicit receiver scope:This naming also plays nicely with the following pattern of using
MainScope()by interface delegation: