Question
What to Use Instead of Deprecated Handler() in Android Kotlin
Question
In Android Kotlin, how can I fix the deprecation warning in the following code? Also, what are the recommended alternatives for delaying code execution?
Handler().postDelayed({
context?.let {
// code
}
}, 3000)
Short Answer
By the end of this page, you will understand why Handler() was deprecated, how to replace it correctly using Handler(Looper.getMainLooper()), and when better alternatives such as lifecycleScope, coroutines, or view.postDelayed() are more appropriate in modern Android code.
Concept
In Android, a Handler is used to schedule work to run on a specific thread's message queue. For many years, developers could write Handler() with no arguments, and Android would attach it to the current thread's Looper.
That behavior was deprecated because it is easy to misuse:
- Not every thread has a
Looper - It may not be obvious which thread the
Handleris attached to - It can lead to bugs, crashes, or delayed code running on the wrong thread
The modern approach is to be explicit about which thread should run the code.
For example, if you want to run code on the main UI thread after a delay, use:
Handler(Looper.getMainLooper()).postDelayed({
// code
}, 3000)
This makes your intent clear: the delayed block should run on the main thread.
In modern Android development, however, Handler is often no longer the best first choice. Depending on the situation, you may prefer:
- Coroutines with
delay()for lifecycle-aware async work view.postDelayed()when the work is tied to a specificView- in activities and fragments
Mental Model
Think of a Looper as a worker's inbox and a Handler as the assistant who places tasks into that inbox.
- The thread is the worker
- The Looper is the worker's task queue
- The Handler submits tasks to that queue
postDelayed()says: "Put this task in the queue, but wait 3 seconds first"
The old Handler() constructor was like saying, "Give this task to whoever happens to be nearby." That is risky.
Handler(Looper.getMainLooper()) is like saying, "Give this task specifically to the main UI worker." That is clearer and safer.
Syntax and Examples
Basic replacement for deprecated Handler()
If you want to run delayed code on the main thread:
Handler(Looper.getMainLooper()).postDelayed({
context?.let {
// code
}
}, 3000)
You can also write it with a trailing lambda:
Handler(Looper.getMainLooper()).postDelayed(3000) {
context?.let {
// code
}
}
Using view.postDelayed()
If the delayed work belongs to a specific view:
myButton.postDelayed({
myButton.text = "Done"
}, 3000)
This is convenient when the action is clearly related to that view.
Using coroutines in a Fragment or Activity
Modern Android often uses coroutines instead:
lifecycleScope.launch {
delay(3000)
context?.let {
// code
}
}
This is often easier to read, especially when you already use coroutines.
Why these are better
Handler(Looper.getMainLooper())is explicit about the thread
Step by Step Execution
Consider this code:
Handler(Looper.getMainLooper()).postDelayed({
println("Run after 3 seconds")
}, 3000)
Here is what happens step by step:
Looper.getMainLooper()gets the main thread's message queue.Handler(...)creates a handler attached to that queue.postDelayed(...)schedules the lambda to run later.3000means the task should wait about 3,000 milliseconds.- The main thread continues doing other work.
- After about 3 seconds, the main thread processes the queued task.
- The lambda runs and prints
Run after 3 seconds.
Trace example
println("Start")
Handler(Looper.getMainLooper()).postDelayed({
println("Delayed")
}, 3000)
println("End")
Output order:
Start
End
Delayed
Why?
Startprints immediately- The delayed block is scheduled, not run right away
Endprints immediately after scheduling
Real World Use Cases
Delayed execution is common in Android apps. Some practical examples are:
- Splash screens or short transitions
- Wait briefly before moving to another screen
- Temporary messages
- Hide a banner, tooltip, or loading state after a few seconds
- Debouncing UI actions
- Delay search requests until the user pauses typing
- Retry logic
- Wait before retrying a failed operation
- UI feedback
- Show success text, then revert after a delay
Example: hide a message after 3 seconds
textView.text = "Saved"
textView.postDelayed({
textView.text = ""
}, 3000)
Example: coroutine-based retry delay
lifecycleScope.launch {
try {
api.loadData()
} catch (e: Exception) {
delay(2000)
api.loadData()
}
}
Real Codebase Usage
In real Android projects, developers choose the delayed-execution tool based on ownership and lifecycle.
Common patterns
1. Explicit main-thread Handler
Used when working with Android framework code or older codebases:
private val handler = Handler(Looper.getMainLooper())
fun scheduleWork() {
handler.postDelayed({
// update UI
}, 3000)
}
2. Remove callbacks to avoid leaks
If delayed work should not run after a screen is destroyed:
private val handler = Handler(Looper.getMainLooper())
private val runnable = Runnable {
// work
}
override fun onStart() {
super.onStart()
handler.postDelayed(runnable, 3000)
}
override fun onStop() {
super.onStop()
handler.removeCallbacks(runnable)
}
3. Lifecycle-aware coroutines
Common Mistakes
1. Using deprecated Handler()
Broken code:
Handler().postDelayed({
// code
}, 3000)
Why it is a problem:
- It does not clearly state which thread should run the code
- It triggers a deprecation warning
Fix:
Handler(Looper.getMainLooper()).postDelayed({
// code
}, 3000)
2. Forgetting to import Looper
If you use Looper.getMainLooper(), make sure this import exists:
import android.os.Looper
3. Updating UI after a Fragment or Activity is gone
Broken pattern:
Handler(Looper.getMainLooper()).postDelayed({
textView.text = "Done"
}, 3000)
If the screen is destroyed before 3 seconds pass, this can cause problems.
Safer options:
- Cancel the callback in
onStop()or
Comparisons
| Option | Best for | Lifecycle-aware | Easy to cancel | Notes |
|---|---|---|---|---|
Handler(Looper.getMainLooper()).postDelayed() | Simple delayed work on main thread | No | Yes, with removeCallbacks | Good direct replacement for deprecated Handler() |
view.postDelayed() | Work tied to a specific View | No | Limited, depends on usage | Short and convenient for UI elements |
lifecycleScope.launch { delay(...) } | Modern Android screens | Yes | Yes | Preferred in many Kotlin Android apps |
Cheat Sheet
// Deprecated
Handler()
// Correct explicit replacement
Handler(Looper.getMainLooper())
// Delayed execution
Handler(Looper.getMainLooper()).postDelayed({
// code
}, 3000)
// Kotlin trailing lambda style
Handler(Looper.getMainLooper()).postDelayed(3000) {
// code
}
// View-based delay
myView.postDelayed({
// code
}, 3000)
// Coroutine-based delay
lifecycleScope.launch {
delay(3000)
// code
}
Rules to remember
Handler()with no arguments is deprecated- Be explicit about the
Looper - Use
Looper.getMainLooper()for UI work - Cancel delayed callbacks if the screen can go away
- Prefer lifecycle-aware coroutines in modern Android apps
Useful imports
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
Edge cases
contextin a Fragment may be
FAQ
Why was Handler() deprecated in Android?
Because it was unclear which thread's Looper it would use, making bugs and misuse more likely.
What is the direct replacement for deprecated Handler()?
Use Handler(Looper.getMainLooper()) if the code should run on the main thread.
Should I use Handler or coroutines in Kotlin Android?
If you already use coroutines, lifecycleScope.launch { delay(...) } is often the better modern choice.
Is view.postDelayed() better than Handler?
It can be, if the delayed work is clearly tied to one specific view.
Can postDelayed() cause memory leaks?
Yes. If the callback holds references to a destroyed activity, fragment, or view, it can cause leaks or invalid UI updates.
How do I cancel a delayed Handler task?
Store the Runnable, then call handler.removeCallbacks(runnable).
Can I update the UI from any Handler?
Mini Project
Description
Build a small Android Fragment feature that shows a temporary status message for 3 seconds, then clears it automatically. This demonstrates how to replace deprecated Handler() usage with a safe, explicit approach and how to cancel delayed work when the view goes away.
Goal
Create a Fragment that displays a message, clears it after 3 seconds, and avoids running the callback after the view is destroyed.
Requirements
- Create a
HandlerusingLooper.getMainLooper(). - Show a message in a
TextViewwhen the view is created. - Clear the message after 3 seconds using
postDelayed(). - Store the delayed work in a
Runnableso it can be cancelled. - Remove the callback in
onDestroyView().
Keep learning
Related questions
Android AlarmManager Example: Scheduling Tasks with AlarmManager
Learn how to use Android AlarmManager to schedule tasks, set alarms, and handle broadcasts with a simple beginner example.
Can You Extend a Data Class in Kotlin? Inheritance, Limits, and Better Alternatives
Learn why Kotlin data classes cannot be extended, what causes the component function clash, and which alternatives to use instead.
Difference Between List and Array in Kotlin
Learn the difference between List and Array in Kotlin, including mutability, size, APIs, performance, and when to use each one.