Vincent Tsen
DevNotes by Vincent Tsen

Follow

DevNotes by Vincent Tsen

Follow

GlobalScope vs viewModelScope vs lifecycleScope vs rememberCoroutineScope

Simple app to demonstrate Android Pre-defined Coroutine Scopes Comparisons - when will these Coroutine Scopes be cancelled?

Vincent Tsen's photo
Vincent Tsen
ยทNov 25, 2022ยท

4 min read

GlobalScope vs viewModelScope vs lifecycleScope vs rememberCoroutineScope

Table of contents

  • GlobalScope
  • viewModelScope
  • lifeCycleScope.launch()
  • rememberCoroutineScope
  • What about non-cancellable coroutines?
  • Summary
  • Source Code

The whole point of having these Android pre-defined coroutine scopes is it automatically cancels all the coroutines that are launched in this scope, so you don't need to explicitly cancel them. The exception is GlobalScope survives until process death.

GlobalScope

GlobalScope never gets canceled, even when the activity is destroyed/finished. If you launch a coroutine with GlobalScope, the coroutine runs until it ends. If the coroutine doesn't end, it will keep running either in the background or foreground until the process is killed.

GlobalScope.launch {  
    /*...*/
}

This is very similar to when you create your own custom CoroutineScope. The customCoroutineScope will not be cancelled unless you explicit cancel it.

val customCoroutineScope = CoroutineScope(Dispatchers.Main)
customCoroutineScope.launch {  
    /*...*/
}

Using GlobalScope or create your own custom CoroutineScope is not recommended. I can't think of any good use case for it.

When your app has exited, the coroutine that launched from GlobalScope can still run in the background until the process death (e.g. killed by the operating system)

viewModelScope

You can access the viewModelScope in ViewModel. As you can tell, this viewModelScope is scoped to the lifecycle of the ViewModel. When the ViewModel is destroyed/cleared, this viewModelScope is canceled, all the coroutines from it will be canceled.

viewModelScope.launch {  
    /*...*/
}

To understand the lifecycle of ViewModel, see the following article:

lifeCycleScope.launch()

Depends on where you use the lifeCycleScope, the lifeCycleScope can be bound to the lifecycles of the Activity or the Composable function.

If you use lifeCycleScope in the Activity, the scope is bound to the Activity. It means when Activity is destroyed, lifeCycleScope.cancel() is called. All coroutines belonging to this scope are canceled.

class MainActivity : ComponentActivity() {
    /*...*/  
    fun someFunction() {  
        lifecycleScope.launch { 
            /*...*/ 
        }  
    }  
}

If you use lifeCycleScope in a composable function (where the composable function is not a composable destination, meaning when compose navigation is NOT used), the lifeCycleScope is bound to the lifecycle of the Activity as well.

@Composable  
fun DemoScreen() {  

    val lifeCycleScope = LocalLifecycleOwner.current.lifecycleScope

    Button(onClick = {  
        lifeCycleScope .launch {
           /*...*/
        }
    }) 
}

To retrieve the LifeCycleCoroutineScope in a composable function, you use LocalLifecycleOwner.current.lifecycleScope

However, if the composable function is a composable destination (i.e. when compose navigation is used), the lifeCycleScope is bound to the lifecycle of the composable function (i.e. DemoScreen()).

Similar to the lifecycle of ViewModel, when the composable function is popped out from the back stack (removed from the back stack), the lifeCycleScope is canceled. If the composable function remains in the back stack, all coroutines belong to this lifeCycleScope will not be canceled.

rememberCoroutineScope

rememberCoroutineScope is a composable function that creates a CoroutineScope that bounds to its composable function.

@Composable  
fun DemoScreen() {  

    val rememberCoroutineScope = rememberCoroutineScope()

    Button(onClick = {  
        rememberCoroutineScope.launch {
           /*...*/
        }
    }) 
}

In this example, when DemoScreen leaves the composition, all coroutines belong to this scope will be canceled.

The following scenarios cause the composable function leaves the composition:

  • Press the back button

  • Navigate to a different screen

Please note that putting the app to the background (e.g. pressing the home button) doesn't cause DemoScreen() leaves the composition.

What about non-cancellable coroutines?

Non-cancellable coroutines is bad. Since coroutine cancellation is cooperative, there is no way you can cancel it. Thus, bad coroutine implementation can cause execution leakage. The easiest way to overcome this is to use kotlinx.coroutines.yield().

Summary

Pre-defined Coroutine ScopesWhen coroutines are cancelled?
GlobalScopeNever until process death
viewModelScopeViewModel is destroyed
lifecycleScope (in Activity / not in composable destination)Activity is destroyed
lifecycleScope (in composable destination)Composable destination is pop out from the back stack
rememberCoroutineScopeThe composable function leaves the composition

lifeCycleScope vs rememberCoroutineScope

When lifeCycleScope is used in composable destination, it behaves kind of similarly to rememberCoroutineScopewith the following differences.

ScenarioslifeCycleScope (composable destination)rememerCoroutineScope
Navigate forwardCoroutines remain (still in the back stack)Coroutines are canceled (leaving composition)
Navigate backwardCoroutines are canceled (removed from the back stack)Coroutines are canceled (leaving composition)
App moves to backgroundCoroutines remain (still in the back stack)Coroutines remain (still in composition)

When lifeCycleScope(composable destination) is canceled?

  • Navigate backward

When rememerCoroutineScope is canceled?

  • Navigate backward and forward

When the app moves to the background, both lifeCycleScope and rememerCoroutineScope won't be canceled.

In fact, all these pre-defined coroutine scopes are wasting resources and memory because it keeps running in the background even though there isn't anything to update on the UI.

The solutions could be using lifeCycleScope.launchWhenStarted() or lifecycle.repeatOnLifeCycle(). See the following article for details:

lifeCycleScope.launchWhenResumed() is not ideal because we still want to update the UI when the app is visible in the background.

Source Code

GitHub Repository: Demo_CoroutineScope

This demo app excludes the lifeCycleScope with composable destination.

Did you find this article valuable?

Support Vincent Tsen by becoming a sponsor. Any amount is appreciated!

See recent sponsors |ย Learn more about Hashnode Sponsors
ย 
Share this