GlobalScope vs viewModelScope vs lifecycleScope vs rememberCoroutineScope

vtsen

Vincent Tsen

Posted on December 16, 2022

GlobalScope vs viewModelScope vs lifecycleScope vs rememberCoroutineScope

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

This is part of the Kotlin coroutines series:

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 {  
    /*...*/
}
Enter fullscreen mode Exit fullscreen mode

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

val customCoroutineScope = CoroutineScope(Dispatchers.Main)
customCoroutineScope.launch {  
    /*...*/
}
Enter fullscreen mode Exit fullscreen mode

Using GlobalScope or create your 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, and all the coroutines from it will be canceled.

viewModelScope.launch {  
    /*...*/
}
Enter fullscreen mode Exit fullscreen mode

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 { 
            /*...*/ 
        }  
    }  
}
Enter fullscreen mode Exit fullscreen mode

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 {
           /*...*/
        }
    }) 
}
Enter fullscreen mode Exit fullscreen mode

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 {
           /*...*/
        }
    }) 
}
Enter fullscreen mode Exit fullscreen mode

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 Scopes When coroutines are cancelled?
GlobalScope Never until process death
viewModelScope ViewModel 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
rememberCoroutineScope The 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.

Scenarios lifeCycleScope (composable destination) rememerCoroutineScope
Navigate forward Coroutines remain (still in the back stack) Coroutines are canceled (leaving composition)
Navigate backward Coroutines are canceled (removed from the back stack) Coroutines are canceled (leaving composition)
App moves to background Coroutines 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 in 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.


Originally published at https://vtsen.hashnode.dev.

💖 💪 🙅 🚩
vtsen
Vincent Tsen

Posted on December 16, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related