How to create Life Cycle Aware Handler in Android?

Viewed 1464

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!

3 Answers

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.

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

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) }
Related