How to create Life Cycle Aware Handler in Android?
Asked Answered
H

4

6

First of all, I know how to create Handler.

I am working on a project where I am using Handler with postDelayed. There are some times when app got crashed because activity was destroyed and the task inside the handler executes after activity destroy.

I am looking for an alternative of Handler which can execute after a delay and it could be Lifecycle Aware so that the app won't get crash.

I know how to cancel Handler (removing Handler or cancelling handler inside onDestroy/onStop methods of activity), here is the link for the same. But I am not looking for these solutions. Any alternative would be a better solution if one can have.

Thanks in advance!

Hypnotist answered 12/1, 2021 at 12:1 Comment(1)
add a boolean in onPause, onResume. If its false, do not run the codeIb
M
4

Depending on if you're using java or Kotlin, you can use RxJava or coroutines for this.

RxJava solution

// this should be a member variable
private final CompositeDisposable disposables = new CompositeDisposable();

// this is how you launch the task that needs delay
Disposable d = Single.timer(2, TimeUnit.SECONDS)
    .subscribeOn(Schedulers.io())
    .observeOn(schedulers.ui())
    .subscribe(ignored -> {
        // you can manipulate the ui here
     });
        
// make sure to call disposables.clear() in onDestroyView
disposables.add(d);

Kotlin solution

viewLifecycleOwner.lifecycleScope.launchWhenResumed {
   withContext(Dispatchers.IO) {
       delay(2000)
   }
   // you can manipulate the ui here
}

As you can see the Kotlin + coroutines solution requires much less manual work, and it's harder to get wrong, so if you're on a Kotlin project I think you should use that one. Other alternative may be to use Guava ListenableFutures but I haven't work with those yet.

Myogenic answered 12/1, 2021 at 12:15 Comment(3)
There's no point in specifying a dispatcher to call a suspend function like delay(). It is the responsibility of the suspend function not to block and delegate itself if necessary.Jaconet
Should we still be using RxJava?Heiner
@Heiner If you and your team already know it then yes ,it would be a fine choice. Or if your project is still mostly java then it's also a great choice. If you're in a greenfield project, and have the luxury to choose the tech, then I would suggest coroutines.Myogenic
B
1

if you are familiar and ok with using coroutines, you can replace Handlers to achieve the same functionality

using below dependency with coroutines you can make coroutines lifecycle aware

implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"

then in Activity

lifeCycleScope.launchWhenStarted{
     delay(1000)

     //do your work after delay, runs on main thread 
     //by default, will be cancelled if life cycle is inactive
}

More about using coroutines : Deep Dive into Coroutines + Android

Burchett answered 12/1, 2021 at 12:10 Comment(3)
There's no point in specifying a dispatcher to call a suspend function like delay(). It is the responsibility of the suspend function not to block and delegate itself if necessary.Jaconet
Makes sense, delay function in library will handle executing it on worker by default.Burchett
This is simple, and the cancellation will work well, exactly because of the delay(). But for some reason launchWhenStarted is deprecated without offering a good replacement. I do not understand why they offer repeatOnLifecycle, which is not doing the same.Marston
H
1

If you are using a Handler to execute delayed actions with postDelayed() you can run into troubles when the execution of the action happens after your Activity or Fragment has been destroyed.

There is a simple solution to this. Bind your Handler to the lifecycle.

Create a LifecycleObserver

First lets create a LifecycleObserver that gets a Handler instance. In the event of Lifecycle.Event.ON_DESTROY it will remove all callbacks and messages from that Handler.

class LifecycleObververHandler(private val handler: Handler) : LifecycleObserver {
    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    internal fun onDestroy() {
        handler.removeCallbacksAndMessages(null)
    }
}

Add the LifecycleObserver to the LifecycleOwner

Next we have to add the LifecycleObververHandler to a LifecycleOwner. We also wanna create these lifecycle observed handlers easily. So lets create a LifecycleHandlerFactory.

That factory gets created with a lambda handlerFactory that gives you an instance of a Handler (default is a Handler with a main Looper). It has one function create that expects a LifecycleOwner.

Within that function it checks that the state of the Lifecycle is not DESTROYED. It calls the handlerFactory to get an instance of Handler. Then it creates a LifecycleObserverHandler, which takes the handler, and adds that Observer to the LifecycleOwner. Finally the Handler gets returned.

class LifecycleHandlerFactory(private val handlerFactory: (() -> Handler) = { Handler(Looper.getMainLooper()) }) {

    fun create(owner: LifecycleOwner): Handler {
        check(owner.lifecycle.currentState != Lifecycle.State.DESTROYED) {
            "Cannot create a Handler for a destroyed life-cycle"
        }
        val handler = handlerFactory.invoke()
        val observer = LifecycleObververHandler(handler)
        owner.lifecycle.addObserver(observer)
        return handler
    }
}

Inject the lifecycle aware Handler

When you are using a DependendencyInjection Framework or a service locater like Koin you can inject the lifecycle aware Handler.

module {
  // a single instance of LifecycleHandlerFactory
  // it gets a lambda that every time its being called returnes a new Handler with a main looper.
  single { LifecycleHandlerFactory() }
  
  // uses the LifecycleHandlerFactory to create a new handler with a LifecycleOwner as parameter.
  factory<Handler> { (lifecycleOwner: LifecycleOwner) -> get<LifecycleHandlerFactory>().create(lifecycleOwner) }
}

Finally you can inject a lifecycle handler in your Fragment (or Activity).

// injects a new handler with a LifecycleOwner as a parameter
private val handler: Handler by inject { parametersOf(viewLifecycleOwner) }
Halm answered 12/1, 2021 at 12:36 Comment(0)
D
0

launchWhenResumed () is deprected now, seems a solution now would be something like:

    lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.RESUMED) {
            delay(2000)
            yourFunction()
        }
    }

You can use the following little helper:

fun LifecycleOwner.delayed(
    timeMillis: Long,
    state: Lifecycle.State = Lifecycle.State.RESUMED,
    block: () -> Unit
) {
    lifecycleScope.launch {
        repeatOnLifecycle(state) {
            delay(timeMillis)
            block()
        }
    }
}

Like in:

delayed(2000) { yourFunction() }
Deneendenegation answered 29/4, 2023 at 5:46 Comment(1)
As the name repeatOnLifecycle suggests, this will repeat on each resume.Marston

© 2022 - 2025 — McMap. All rights reserved.