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.
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.
repeatOnLifecycle
will ...repeat. launchWhenResumed
will not. –
Stauder Create an extension function in a separate file or same
fun LifecycleOwner.launchWhenResumed(callback: () -> Unit) {
lifecycleScope.launch { lifecycle.withResumed(callback) }
}
Usage:
launchWhenResumed {
// your Code..
}
withResumed
opens a non suspend-able block, therefore you'd need to launch again –
Sukiyaki 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
.
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 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!).
You can use:
fun Fragment.launchWhenResumed(block: suspend () -> Unit) {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
block()
}
}
}
And then in Fragment:
launchWhenResumed {
}
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]()
}
}
}
}
}
}
© 2022 - 2024 — McMap. All rights reserved.
StateFlow
) this difference doesn't matter. Based on your use case maybe we can suggest a different alternative. – Parsifal