5

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!

Kishan Solanki
  • 13,761
  • 4
  • 85
  • 82

4 Answers4

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.

A. Patrik
  • 1,530
  • 9
  • 20
  • 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. – Tenfour04 Jan 12 '21 at 14:36
  • Should we still be using RxJava? – IgorGanapolsky Oct 08 '21 at 16:27
  • @IgorGanapolsky 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. – A. Patrik Oct 11 '21 at 08:25
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

Rajan Kali
  • 12,627
  • 3
  • 25
  • 37
  • 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. – Tenfour04 Jan 12 '21 at 14:35
  • Makes sense, delay function in library will handle executing it on worker by default. – Rajan Kali Jan 13 '21 at 04:44
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) }
ChristianB
  • 2,452
  • 2
  • 10
  • 22
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() }
Oliver Hoffmann
  • 146
  • 2
  • 11