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

vtsen

Vincent Tsen

Posted on December 23, 2022

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

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

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

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

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 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

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


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

💖 💪 🙅 🚩
vtsen
Vincent Tsen

Posted on December 23, 2022

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

Sign up to receive the latest update from our blog.

Related