Android
method delay
handler
postDelayed
programming tutorial

How to call a method after a delay in Android

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Delaying method execution in Android is easy, but the correct API depends on which thread should run the work and how long the owning component may stay alive. For UI work, the safest default is scheduling on the main thread and cleaning it up when the Activity or Fragment is destroyed.

Use Handler.postDelayed for Main-Thread Work

If the delayed method updates the UI, post it to the main thread with a Handler tied to the main Looper.

kotlin
1import android.os.Bundle
2import android.os.Handler
3import android.os.Looper
4import androidx.appcompat.app.AppCompatActivity
5
6class MainActivity : AppCompatActivity() {
7    private val handler = Handler(Looper.getMainLooper())
8
9    override fun onCreate(savedInstanceState: Bundle?) {
10        super.onCreate(savedInstanceState)
11
12        handler.postDelayed({
13            showMessage()
14        }, 3_000)
15    }
16
17    private fun showMessage() {
18        title = "Delay finished"
19    }
20}

This is the classic Android pattern for short UI delays such as hiding a splash screen element, showing a tooltip later, or deferring a button state change.

Remove Callbacks When the Screen Goes Away

Delayed work can outlive the screen that scheduled it. If that work still tries to touch UI state, you risk crashes or leaks. Remove pending callbacks in a lifecycle method.

kotlin
1class MainActivity : AppCompatActivity() {
2    private val handler = Handler(Looper.getMainLooper())
3    private val delayedAction = Runnable { showMessage() }
4
5    override fun onStart() {
6        super.onStart()
7        handler.postDelayed(delayedAction, 3_000)
8    }
9
10    override fun onStop() {
11        handler.removeCallbacks(delayedAction)
12        super.onStop()
13    }
14
15    private fun showMessage() {
16        title = "Still visible"
17    }
18}

That small cleanup step is what makes delayed execution production-safe instead of demo-safe.

Coroutines Are Cleaner in Modern Kotlin Apps

If your project already uses Kotlin coroutines, delay inside a lifecycle-aware scope is often more readable than raw handlers.

kotlin
1import android.os.Bundle
2import androidx.appcompat.app.AppCompatActivity
3import androidx.lifecycle.lifecycleScope
4import kotlinx.coroutines.delay
5import kotlinx.coroutines.launch
6
7class MainActivity : AppCompatActivity() {
8    override fun onCreate(savedInstanceState: Bundle?) {
9        super.onCreate(savedInstanceState)
10
11        lifecycleScope.launch {
12            delay(3_000)
13            showMessage()
14        }
15    }
16
17    private fun showMessage() {
18        title = "Coroutine delay finished"
19    }
20}

This is a strong default in Kotlin-first codebases because the coroutine is automatically cancelled with the lifecycle scope.

Use Background Scheduling for Non-UI Work

If the delayed method should not run on the main thread, use a scheduler that fits background work rather than forcing everything through the UI thread.

kotlin
1import java.util.concurrent.Executors
2import java.util.concurrent.TimeUnit
3
4val scheduler = Executors.newSingleThreadScheduledExecutor()
5
6scheduler.schedule({
7    println("Background task ran after delay")
8}, 3, TimeUnit.SECONDS)

This is useful for non-UI tasks such as delayed cache cleanup or a network retry strategy. If you later need to update the UI, switch back to the main thread explicitly.

CountDownTimer Is for Repeated Ticks

CountDownTimer works, but it is best when you need periodic callbacks before completion rather than a single delayed method.

kotlin
1import android.os.CountDownTimer
2
3val timer = object : CountDownTimer(5_000, 1_000) {
4    override fun onTick(millisUntilFinished: Long) {
5        println("Remaining: $millisUntilFinished")
6    }
7
8    override fun onFinish() {
9        println("Finished")
10    }
11}
12
13timer.start()

If you only need one callback after a delay, postDelayed or coroutines are usually simpler.

Pick the API by Intent

A practical rule is:

  • UI delay on the main thread: Handler.postDelayed
  • modern Kotlin UI code: lifecycleScope.launch plus delay
  • repeated countdown behavior: CountDownTimer
  • non-UI delayed background work: ScheduledExecutorService

That decision is more important than memorizing every possible scheduling API in the Android framework.

Common Pitfalls

The most common mistake is scheduling delayed work and never cancelling it when the Activity or Fragment stops. That can keep references alive and trigger UI calls after the screen is gone.

Another issue is using a background scheduler for code that directly updates views. UI changes belong on the main thread.

Developers also sometimes use CountDownTimer for a one-shot delay even though it adds complexity they do not need.

Finally, remember that long-running background tasks may be interrupted by process death or app lifecycle changes. A delayed in-memory callback is not the same thing as guaranteed background execution.

Summary

  • Use Handler.postDelayed for simple delayed UI work on the main thread.
  • Remove pending callbacks when the owning screen is no longer active.
  • Prefer coroutines with lifecycleScope in modern Kotlin apps.
  • Use background schedulers only for non-UI delayed work.
  • Choose the delay mechanism based on lifecycle and thread requirements, not convenience alone.

Course illustration
Course illustration

All Rights Reserved.