launchWhenCreated() vs launchWhenStarted() vs launchWhenResumed() vs repeatOnLifeCycle()

Investigating and experimenting various LifecycleCoroutineScope launchWhenX() and repeatOnLifeCycle() functions

launchWhenCreated() vs launchWhenStarted() vs launchWhenResumed() vs repeatOnLifeCycle()

This is part of the Kotlin coroutines series:

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 functionsWhen coroutines are started?When coroutines are suspended?When coroutines are resumed?When coroutines cancelled?
launchWhenCreated()ON_CREATEN/AN/AON_DESTROY
launchWhenStarted()ON_STARTON_STOPON_STARTON_DESTROY
launchWhenResumed()ON_RESUMEON_PAUSEON_RESUMEON_DESTROY

Depends on when you call launchWhenX(), it automatically starts the coroutine when your current lifecycle state equals or above the target X 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 StatesApp Visibility Status
CREATEDNOT visible
STARTEDVisible at background
RESUMEDVisible 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 FunctionsWhen coroutines are started?When coroutines are suspended?When coroutines are resumed?When coroutines are canceled?
launchWhenStarted()ON_STARTON_CREATESTARTON_DESTROY
repeatOnLifecycle(Lifecycle.State.STARTED)ON_STARTN/AN/AON_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 FunctionsApp Is Not Visible (ON_STOP)App Becomes Visible (ON_START)
launchWhenStarted()Coroutines suspendedCoroutines resumed
repeatOnLifecycle(Lifecycle.State.STARTED)Coroutines canceledCoroutines 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 coroutines

  • repeatOnLifecycle(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

Did you find this article valuable?

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