Question
Android onBackPressed Deprecation: Modern Back Handling in Kotlin
Question
I upgraded targetSdkVersion and compileSdkVersion to 33, and now I get a warning that onBackPressed() is deprecated.
The documentation and examples suggest using either android.window.OnBackInvokedCallback or androidx.activity.OnBackPressedCallback for back navigation handling. How do I migrate my existing code to the newer approach?
Here is my current use case: inside onBackPressed() I check isTaskRoot to determine whether the activity is the last one in the task stack. If it is, I open Dashboard; otherwise, I finish the current activity and return a result.
override fun onBackPressed() {
if (isTaskRoot) {
// This activity is the last one in the task stack
// for example, when opened from a push notification
startActivity(Intent(this, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
Short Answer
By the end of this page, you will understand why onBackPressed() was deprecated, when to use OnBackPressedDispatcher with OnBackPressedCallback, and how to convert older Activity back-button logic to the modern Android approach in Kotlin. You will also see how this fits into real apps and how to avoid common migration mistakes.
Concept
onBackPressed() used to be the common way to intercept the system back button in an Android Activity. However, Android introduced a more modern back navigation system so back handling can be lifecycle-aware, easier to compose, and more consistent with newer Android behavior.
For most apps, the recommended replacement is:
onBackPressedDispatcherOnBackPressedCallback
These come from AndroidX and work well across Android versions.
Why was onBackPressed() deprecated?
The old method had some limitations:
- It tightly coupled back handling to a single overridden Activity method.
- It was less flexible for Fragments and nested UI components.
- It did not fit as well with newer predictive and system-managed back behavior.
- It made lifecycle-safe registration harder.
What should you use instead?
In most Activity and Fragment code, use:
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// custom back logic
}
})
This registers a callback that runs when the user presses back.
What about OnBackInvokedCallback?
OnBackInvokedCallback is part of the newer platform-level back API introduced for newer Android versions. In day-to-day app development, you usually do not need to use it directly unless you are working with platform-specific behavior. For most apps, androidx.activity.OnBackPressedCallback is the practical and recommended solution because it provides backward compatibility and integrates with AndroidX components.
Applying it to your case
Your old logic does two things:
- If this Activity is the root of the task, open
Dashboardand close the current Activity. - Otherwise, finish the current Activity with a result.
That logic does not need to change. Only the place where it is handled changes: instead of overriding onBackPressed(), you register an OnBackPressedCallback.
Mental Model
Think of the back button like a queue of handlers.
In older Android code, the Activity had one main back-button method: onBackPressed(). It was like saying, "When back is pressed, ask the Activity what to do."
With the newer system, back handling is more like a list of listeners:
- Android asks the top active back callback first.
- If that callback handles the event, the process stops.
- If not, Android can continue to the next handler or default system behavior.
A good analogy is a stack of sticky notes on a screen:
- The top note gets checked first.
- If it has instructions, those are used.
- If not, Android moves on.
This makes back handling easier for screens, dialogs, Fragments, and reusable UI parts.
Syntax and Examples
The modern syntax in an Activity looks like this:
import android.content.Intent
import android.os.Bundle
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
class DetailsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (isTaskRoot) {
startActivity(Intent(this@DetailsActivity, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
})
}
private fun finishWithResultOK() {
setResult(RESULT_OK)
finish()
}
}
What this does
addCallback(this, ...)attaches the callback to the Activity lifecycle.truemeans the callback starts enabled.handleOnBackPressed()is the new place for your custom back logic.
A simpler example
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
finish()
}
})
This simply closes the Activity when back is pressed.
Rewriting your original code
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (isTaskRoot) {
startActivity(Intent(this@MyActivity, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
})
This is the direct modern equivalent of your deprecated onBackPressed() override.
Step by Step Execution
Consider this example:
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (isTaskRoot) {
startActivity(Intent(this@MyActivity, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
})
Step-by-step
- The Activity starts.
- In
onCreate(), a back callback is registered. - The user presses the system back button.
- Android sends the event to
onBackPressedDispatcher. - The dispatcher finds your enabled callback.
handleOnBackPressed()runs.- The code checks
isTaskRoot.
Case 1: isTaskRoot == true
- This means the current Activity is the root of the task.
Dashboardis started.finish()closes the current Activity.
Case 2: isTaskRoot == false
- The Activity is not the only one in the task stack.
finishWithResultOK()is called.- That method sets the result and closes the Activity.
Why this@MyActivity is used
Inside the anonymous callback object, plain this refers to the callback object itself, not the Activity. So to pass the correct Context to Intent, you use:
this@MyActivity
Real World Use Cases
Modern back handling is used in many practical Android scenarios:
- Form screens: ask the user whether they want to discard unsaved changes.
- Search screens: close search mode before leaving the screen.
- Bottom sheets or dialogs: collapse or dismiss UI before exiting the Activity.
- Push notification flows: redirect users to a safe home screen if the Activity was opened directly.
- Multi-step flows: go back one step inside the screen rather than immediately finishing the Activity.
- Fragment-based apps: let the visible Fragment intercept back before the Activity handles it.
Your isTaskRoot example is common in notification-driven navigation. If a user lands directly on a detail screen from a push notification, pressing back may need to take them to Dashboard instead of leaving the app in an odd state.
Real Codebase Usage
In real Android projects, developers often use OnBackPressedCallback with a few common patterns.
1. Guard clauses
Keep back logic easy to read by returning early.
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (isTaskRoot) {
startActivity(Intent(this@MyActivity, Dashboard::class.java))
finish()
return
}
finishWithResultOK()
}
})
2. Validation before leaving
override fun handleOnBackPressed() {
if (hasUnsavedChanges()) {
showDiscardDialog()
return
}
finish()
}
3. Enabling and disabling callbacks
A callback can be turned on only when needed.
val callback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
closeSelectionMode()
}
}
onBackPressedDispatcher.addCallback(this, callback)
callback.isEnabled = true
4. Fragment-friendly back handling
In real codebases, many teams register callbacks in Fragments instead of Activities so each screen controls its own behavior.
5. Fallback to default behavior
If you want custom handling sometimes and default behavior at other times, you can temporarily disable the callback and re-dispatch:
override fun handleOnBackPressed() {
if (shouldUseDefaultBack()) {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
isEnabled = true
return
}
showExitDialog()
}
This pattern is useful when your custom logic only applies in certain states.
Common Mistakes
1. Using this instead of the Activity context
Broken example:
startActivity(Intent(this, Dashboard::class.java))
Inside the callback, this refers to the OnBackPressedCallback, not the Activity.
Correct:
startActivity(Intent(this@MyActivity, Dashboard::class.java))
2. Registering the callback without lifecycle awareness
Prefer:
onBackPressedDispatcher.addCallback(this, callback)
Using the Activity or Fragment as the lifecycle owner helps Android remove the callback at the right time.
3. Forgetting that the callback is enabled or disabled
If you create a callback with false, it will not run.
object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
finish()
}
}
Make sure isEnabled is set correctly.
4. Calling deprecated onBackPressed() from new code
Broken idea:
override fun onBackPressed() {
// old pattern
}
If you are migrating, move the logic into handleOnBackPressed() instead.
5. Accidentally causing duplicate navigation
Broken example:
override fun handleOnBackPressed() {
startActivity(Intent(this@MyActivity, Dashboard::class.java))
finish()
finishWithResultOK()
}
Only perform one back action path.
6. Ignoring task-stack behavior
If your screen can be opened from multiple entry points, test all cases:
- opened from inside the app
- opened from a notification
- opened from a deep link
Your isTaskRoot logic may behave differently depending on how the Activity was launched.
Comparisons
| Approach | When to use | Pros | Cons |
|---|---|---|---|
onBackPressed() | Legacy code only | Simple and familiar | Deprecated, less flexible |
OnBackPressedDispatcher + OnBackPressedCallback | Most modern apps | Lifecycle-aware, AndroidX-friendly, works well with Fragments | Slightly more setup |
OnBackInvokedCallback | Platform-specific newer back integration | Matches newer Android platform behavior | Usually not necessary directly for most app code |
onBackPressed() vs OnBackPressedCallback
onBackPressed()is one overridden Activity method.OnBackPressedCallbackis a registered handler that can be enabled, disabled, and scoped to a lifecycle.
Activity override vs Fragment callback
| Location | Best for |
|---|---|
| Activity | App-wide or screen-container back behavior |
| Fragment | Screen-specific back behavior |
finish() vs custom back logic
| Option | What it does |
|---|---|
finish() | Closes the current Activity |
finishWithResultOK() | Closes the Activity and returns a result |
Start Dashboard then finish() | Redirects the user to a safe destination before closing |
Cheat Sheet
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// custom back logic
}
})
Rules
- Use
OnBackPressedCallbackinstead of overridingonBackPressed(). - In Activities, register the callback in
onCreate(). - In Fragments, register with
viewLifecycleOwnerwhen appropriate. - Use
this@MyActivityinside the callback when you need the Activity context. truemeans enabled;falsemeans disabled.
Common migration pattern
Old:
override fun onBackPressed() {
if (isTaskRoot) {
startActivity(Intent(this, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
New:
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (isTaskRoot) {
startActivity(Intent(this@MyActivity, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
})
Edge cases
- If custom logic should not run, disable the callback and call
onBackPressedDispatcher.onBackPressed(). - Test behavior when the Activity is launched from notifications or deep links.
- Be careful with multiple callbacks in nested UI structures.
FAQ
What is the replacement for onBackPressed() in Android?
For most apps, use onBackPressedDispatcher with OnBackPressedCallback. It is the recommended AndroidX alternative.
Should I use OnBackInvokedCallback or OnBackPressedCallback?
In most app code, use OnBackPressedCallback. It is simpler, AndroidX-based, and works across Android versions.
Where should I register OnBackPressedCallback in an Activity?
Usually in onCreate(), using the Activity as the lifecycle owner.
Can I still check isTaskRoot with the new API?
Yes. Your business logic stays the same. Only the back-handling mechanism changes.
Why does Intent(this, Dashboard::class.java) fail inside the callback?
Because this refers to the callback object, not the Activity. Use this@MyActivity instead.
Does OnBackPressedCallback work with Fragments?
Yes. It is commonly used in Fragments and is one of the reasons the newer approach is preferred.
How do I let the default back behavior happen sometimes?
Temporarily disable the callback, call onBackPressedDispatcher.onBackPressed(), then enable it again if needed.
Mini Project
Description
Build an Android Activity that handles the system back button using the modern API. The screen should decide whether to return to a dashboard or finish with a result based on whether it is the root of the task. This mirrors a common real-world case where a screen may be opened from inside the app or directly from a push notification.
Goal
Create a Kotlin Activity that replaces deprecated onBackPressed() logic with OnBackPressedCallback and preserves the original navigation behavior.
Requirements
- Create an Activity that registers a back callback using
onBackPressedDispatcher - If
isTaskRootis true, openDashboardActivityand close the current Activity - If
isTaskRootis false, returnRESULT_OKand finish the Activity - Use the correct Activity context when creating the
Intent - Keep the code lifecycle-aware by attaching the callback to the Activity
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.