launchWhenCreated() vs launchWhenStarted() vs launchWhenResumed() vs repeatOnLifeCycle()
Investigating and experimenting various LifecycleCoroutineScope launchWhenX() and repeatOnLifeCycle() functions
This is part of the Kotlin coroutines series:
Part 3 - GlobalScope vs viewModelScope vs lifecycleScope vs rememberCoroutineScope
Part 4 - launchWhenCreated() vs launchWhenStarted() vs launchWhenResumed() vs repeatOnLifeCycle()
In the previous article, we learned about LifeCycleCoroutineScope.launch(). However, there are a few additional functions in LifeCycleCoroutineScope
:
launchWhenCreated()
launchWhenStarted()
launchWhenResumed()
launchWhenX()
The code usage looks like this:
@Composable
fun DemoScreen() {
val lifeCycleScope = LocalLifecycleOwner.current.lifecycleScope
Button(onClick = {
lifeCycleScope.launchWhenStarted {
/*...*/
}
})
}
The benefit of using this launchWhenX()
APIs is it can automatically start, suspend and resume the coroutines for you. The table below shows you in what lifecycle event, the coroutines are started, suspended, resumed and canceled.
launchWhenX functions | When coroutines are started? | When coroutines are suspended? | When coroutines are resumed? | When coroutines cancelled? |
launchWhenCreated() | ON_CREATE | N/A | N/A | ON_DESTROY |
launchWhenStarted() | ON_START | ON_STOP | ON_START | ON_DESTROY |
launchWhenResumed() | ON_RESUME | ON_PAUSE | ON_RESUME | ON_DESTROY |
Depends on when you call
launchWhenX()
, it automatically starts the coroutine when your current lifecycle state equals or above the targetX
lifecycle state.
The issue with launchWhenCreated()
is when the lifecycle is destroyed, the LifeCycleCoroutineScope
cancels all the coroutines. Since there are no more coroutines, there is nothing to be suspended or resumed.
To understand the lifecycle in detail, please read the following article:
Here is the summary of app visibility status corresponding to its lifecycle state:
Lifecycle States | App Visibility Status |
CREATED | NOT visible |
STARTED | Visible at background |
RESUMED | Visible at foreground |
Given all these functions above, it looks like launchWhenStarted()
should be used because it doesn't waste any resources when your app is not visible. You can use launchWhenResumed()
, but it suspends the coroutine when your app is still visible in the background, which we don't want.
launchWhenStarted() vs repeatOnLifeCycle()
But, wait! Isn't launchWhenStarted()
same as repeatOnLifeCycle(Lifecycle.State.STARTED)
?
@Composable
fun DemoScreen() {
val lifeCycle = LocalLifecycleOwner.current.lifecycle
val lifeCycleScope = LocalLifecycleOwner.current.lifecycleScope
Button(onClick = {
lifeCycleScope .launch {
lifeCycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
/*...*/
}
}
})
}
Here is a summary of launchWhenStarted()
vs repeatOnLifecycle(Lifecycle.State.STARTED)
comparisons:
Launch Functions | When coroutines are started? | When coroutines are suspended? | When coroutines are resumed? | When coroutines are canceled? |
launchWhenStarted() | ON_START | ON_CREATE | START | ON_DESTROY |
repeatOnLifecycle(Lifecycle.State.STARTED) | ON_START | N/A | N/A | ON_STOP |
As you can see, repeatOnLifecycle(Lifecycle.State.STARTED)
doesn't suspend or resume the coroutines. When the lifecycle state moves below the STARTED state, it cancels all the coroutines. When it moves to STARTED state again, it starts the coroutine again.
On the other hand, launchWhenStarted()
doesn't cancel the coroutines, but it suspends the coroutines instead.
Launch Functions | App Is Not Visible (ON_STOP) | App Becomes Visible (ON_START) |
launchWhenStarted() | Coroutines suspended | Coroutines resumed |
repeatOnLifecycle(Lifecycle.State.STARTED) | Coroutines canceled | Coroutines started again |
In other words, what happens if the app moves to the background (app is not visible), then moves to the foreground (app is visible) again?
launchWhenStarted()
suspends and resumes coroutinesrepeatOnLifecycle(Lifecycle.State.STARTED)
restarts the coroutines
If your coroutine count from 0 → 10000, repeatOnLifecycle(Lifecycle.State.STARTED)
simply restarts the counter and starts from 0 again. Since launchWhenStarted()
doesn't restart the coroutines, it appears it is the better option here.
Conclusion
Google advocates repeatOnLifecycle(Lifecycle.State.STARTED)
over launchWhenStarted()
because launchWhenStarted()
is not safe to collect, it keeps emitting in the background when the UI is not visible. However, I don't see this behavior based on the experiment that I have done.
It seems to me both launchWhenStarted()
and repeatOnLifecycle(Lifecycle.State.STARTED)
are safe to collect. Depending on your need, launchWhenStarted()
suspends and resumes the coroutine, and repeatOnLifecycle(Lifecycle.State.STARTED)
restarts the coroutine.
You can also use them for collecting flow. See this article for details:
Source Code
GitHub Repository: Demo_CoroutineScope