Kotlinx.coroutines: Provide MainScope factory

Created on 13 Nov 2018  路  1Comment  路  Source: Kotlin/kotlinx.coroutines

We should provide factory method fun MainScope() = CoroutineScope(Dispatchers.Main + SupervisorJob()).

It fixes multiple issues (especially with the #828):

  1. Integration with UI components are simple both for implementing an interface and having it in a field
  2. It protects newcomers from async behaviour when it is launched from UI component
  3. It simplifies guides and documentation
enhancement

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":

class MainViewModel(private val repository: TitleRepository) : ViewModel() {
    // ... 
    private val uiScope = MainScope()

    override fun onCleared() {
        super.onCleared()
        uiScope.cancelCoroutineScope()
    } 
}

This gives the following benefits:

  1. It avoids introducing the concepts of Job and CoroutineContext to novice developers.

  2. 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()
    } 
}

>All comments

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":

class MainViewModel(private val repository: TitleRepository) : ViewModel() {
    // ... 
    private val uiScope = MainScope()

    override fun onCleared() {
        super.onCleared()
        uiScope.cancelCoroutineScope()
    } 
}

This gives the following benefits:

  1. It avoids introducing the concepts of Job and CoroutineContext to novice developers.

  2. 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()
    } 
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

serebit picture serebit  路  37Comments

PaulWoitaschek picture PaulWoitaschek  路  47Comments

elizarov picture elizarov  路  62Comments

fvasco picture fvasco  路  70Comments

SUPERCILEX picture SUPERCILEX  路  40Comments