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:
- Part 1 - Kotlin Coroutines Basics - Simple Android App Demo
- Part 2 - kotlinx.coroutines.delay() vs Thread.sleep()
- Part 3 - GlobalScope vs viewModelScope vs lifecycleScope vs rememberCoroutineScope
- Part 4 - launchWhenCreated() vs launchWhenStarted() vs launchWhenResumed() vs repeatOnLifeCycle()
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 custom CoroutineScope
. The customCoroutineScope
will not be cancelled unless you explicitly cancel it.
val customCoroutineScope = CoroutineScope(Dispatchers.Main)
customCoroutineScope.launch {
/*...*/
}
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 {
/*...*/
}
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 useLocalLifecycleOwner.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 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 rememberCoroutineScope
with 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.
Top comments (0)