Question

Android onBackPressed Deprecation: Modern Back Handling in Kotlin

kotlinandroidnavigationdeprecation-warningandroid-tiramisu

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:

  • onBackPressedDispatcher
  • OnBackPressedCallback

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 Dashboard and 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.

Take Quiz

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.
  • true means 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

  1. The Activity starts.
  2. In onCreate(), a back callback is registered.
  3. The user presses the system back button.
  4. Android sends the event to onBackPressedDispatcher.
  5. The dispatcher finds your enabled callback.
  6. handleOnBackPressed() runs.
  7. The code checks isTaskRoot.

Case 1: isTaskRoot == true

  • This means the current Activity is the root of the task.
  • Dashboard is 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.

Take Quiz

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

ApproachWhen to useProsCons
onBackPressed()Legacy code onlySimple and familiarDeprecated, less flexible
OnBackPressedDispatcher + OnBackPressedCallbackMost modern appsLifecycle-aware, AndroidX-friendly, works well with FragmentsSlightly more setup
OnBackInvokedCallbackPlatform-specific newer back integrationMatches newer Android platform behaviorUsually not necessary directly for most app code

onBackPressed() vs OnBackPressedCallback

  • onBackPressed() is one overridden Activity method.
  • OnBackPressedCallback is a registered handler that can be enabled, disabled, and scoped to a lifecycle.

Activity override vs Fragment callback

LocationBest for
ActivityApp-wide or screen-container back behavior
FragmentScreen-specific back behavior

finish() vs custom back logic

OptionWhat 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
Take Quiz

Cheat Sheet

onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
    override fun handleOnBackPressed() {
        // custom back logic
    }
})

Rules

  • Use OnBackPressedCallback instead of overriding onBackPressed().
  • In Activities, register the callback in onCreate().
  • In Fragments, register with viewLifecycleOwner when appropriate.
  • Use this@MyActivity inside the callback when you need the Activity context.
  • true means enabled; false means 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.

Take Quiz

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 isTaskRoot is true, open DashboardActivity and close the current Activity
  • If isTaskRoot is false, return RESULT_OK and finish the Activity
  • Use the correct Activity context when creating the Intent
  • Keep the code lifecycle-aware by attaching the callback to the Activity
Take Quiz

Keep learning

Related questions

View more