Android: lifecycleScope.launchWhenResumed {} deprecated
Asked Answered
S

6

14

I've been using lifecyclesScope's launchWhenResumed for a long time now, but it seems to be deprecated. The documentation says to use repeatOnLifecycle() but I only want the code to run once, just as it works with the old method.

Scutage answered 4/3, 2023 at 12:3 Comment(2)
what are you using it for? yes, there is a difference, but when collecting a hot flow (StateFlow) this difference doesn't matter. Based on your use case maybe we can suggest a different alternative.Parsifal
Maybe I want to show a toast, access views, anything that requires access from the main threadScutage
I
14

below code do the same functionality as viewLifeCycleOwner.launchWhenResumed

lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
        // do your work here
    }
}

update

I did more research to see what was happening under the hood of the code.

  • viewLifeCycleOwner.launchWhenResumed is a method that can be used to launch a coroutine when the associated view is resumed. When the view is paused or destroyed, the coroutine is cancelled. This method is useful when you want to start a coroutine that is specific to the view's lifecycle.
  • lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) is a method that can be used to automatically start and stop a coroutine based on the lifecycle state of the associated LifecycleOwner. When the LifecycleOwner is resumed, the coroutine is started. When the LifecycleOwner is paused or destroyed, the coroutine is cancelled. This method is useful when you want to start a coroutine that is related to the general lifecycle of the app or a component, and not specific to a particular view.

Google recommends using lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) over viewLifeCycleOwner.launchWhenResumed in order to promote a more efficient and flexible approach to handling lifecycle events.

one of the reasons is that it can handle background tasks more efficiently. When an app goes to the background, the LifecycleOwner associated with a view can still be in the RESUMED state, even though the view is not visible. If you use viewLifeCycleOwner.launchWhenResumed to launch a coroutine in this situation, the coroutine will continue to run even though the user has moved away from the view, which can lead to unnecessary resource consumption.

On the other hand, lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) is designed to automatically stop coroutines when the LifecycleOwner associated with the coroutine is paused or destroyed.When the app goes to the background, the LifecycleOwner associated with the coroutine will typically be paused or destroyed, which means that the coroutine will automatically be cancelled.

Insensibility answered 4/3, 2023 at 16:2 Comment(1)
Unfortunately the docs are misleading in that this function has quite different functionality. repeatOnLifecycle will ...repeat. launchWhenResumed will not.Stauder
P
4

Create an extension function in a separate file or same

fun LifecycleOwner.launchWhenResumed(callback: () -> Unit) {
    lifecycleScope.launch { lifecycle.withResumed(callback) }
}

Usage:

launchWhenResumed { 
    // your Code..
}
Postconsonantal answered 30/3, 2023 at 7:22 Comment(4)
Note: withResumed opens a non suspend-able block, therefore you'd need to launch againSukiyaki
it works for me, better than repeatOnLifecycleBurglary
Check a detailed explain by Manuel Vivo here medium.com/androiddevelopers/…Vicinal
using repeatOnLifecycle will get your scope execute each time after onDestroyView. using withResumed ensuring execute only once. however, you will need to launch again if you need to execute suspend funHellenize
G
3

Just create lazy job bounded to lifecycleScope and start it at the point you want.

fun Lifecycle.performOn(
        state:Lifecycle.State, 
        context:CoroutineContext = EmptyCoroutineContext, 
        block: (suspend CoroutineScope.() -> Unit)
) {
    coroutineScope.launch {
        val job = launch(context, start = CoroutineStart.LAZY, block)
        withStateAtLeast(state) { job.start() }
    }
}

block will be bounded to the scope and also started at the time when the state reached. but remember when you call the function inside the block, at the time suspension resolved lifecycle state might moved to STOP or DESTORY.

Best way is to keep using synchronous execution.
eg: withStateAtLeast, withCreated, withStarted, withResumed.

Gerrard answered 30/3, 2023 at 7:46 Comment(1)
I feel this has similar problems to launchWhenStarted et al in that your suspending block will keep running when your lifecycle has been stopped/paused, e.g. when the current activity moved to the backstack.Keratosis
K
1

You can take a look at one of the recommended solution in this issue tracker comment.

Essentially, you need to think what happens when your suspending code is executing while the lifecycle falls below the expected state. The old approach of launchWhenX paused the coroutine with a custom dispatcher, which could lead to wasted resource, e.g. when activities are put into the background.

The proper alternatives would be to either cancel the execution or to let it keep running until the lifecycle is destroyed. If you need to run a job that must complete, you should probably start your execution in a more general scope, for example in your viewModelScope or GlobalScope (handle with care!).

Keratosis answered 1/6, 2023 at 11:4 Comment(0)
R
1

You can use:

fun Fragment.launchWhenResumed(block: suspend () -> Unit) {
lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
        block()
    }
  }
}

And then in Fragment:

launchWhenResumed { 

 }
Ricks answered 8/5 at 17:11 Comment(0)
H
0

If you want use like lifecycleScope.launchWhenResumed, I recommend to do this:

fun LifecycleOwner.launchWhenResumed(block: suspend CoroutineScope.() -> Unit) {
    lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.RESUMED) {
            block()
            [email protected]()
        }
    }
}

or

inline fun LifecycleOwner.launchWhenResumed(
    retryTime: Int = 1,
    crossinline block: suspend CoroutineScope.() -> Unit
) {
    lifecycleScope.launch {
        var retryCount = 0
        repeatOnLifecycle(Lifecycle.State.RESUMED) {
            try {
                block()
                [email protected]()
            } finally {
                if (retryTime != -1) {
                    retryCount += 1
                    if (retryCount >= retryTime) {
                        [email protected]()
                    }
                }
            }
        }
    }
}
Hullo answered 28/8, 2023 at 16:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.