Handling Back Button Press in Android: Replacing deprecated onBackPressed()
Problem Statement
With the release of Android 13 (API level 33) and newer versions of the AndroidX libraries, the traditional onBackPressed()
method has been deprecated. Developers upgrading their targetSdkVersion
and compileSdkVersion
to 33 or higher now encounter warnings and need to migrate to the new back navigation system.
The deprecated approach looked like this:
override fun onBackPressed() {
if (isTaskRoot) {
startActivity(Intent(mContext, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
Solution Overview
Android now provides two main approaches for handling back navigation:
- OnBackPressedDispatcher (AndroidX) - Recommended for most cases, works back to API level 13
- OnBackInvokedCallback (Platform) - For API level 33+ only
The AndroidX implementation is generally preferred as it provides backward compatibility and integrates seamlessly with other Jetpack components.
Basic Implementation
Kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Handle back button press
if (isTaskRoot) {
startActivity(Intent(this@MainActivity, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
})
}
}
Java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
// Handle back button press
if (isTaskRoot()) {
startActivity(new Intent(MainActivity.this, Dashboard.class));
finish();
} else {
finishWithResultOK();
}
}
});
}
}
Advanced Usage Patterns
Simplified Lambda Syntax (Kotlin)
onBackPressedDispatcher.addCallback(this) {
if (isTaskRoot) {
startActivity(Intent(this@MainActivity, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
Extension Functions for Reusability
Create extension functions to simplify back press handling across your app:
// Activity extension
fun Activity.onBackButtonPressed(callback: (() -> Boolean)) {
(this as? FragmentActivity)?.onBackPressedDispatcher?.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (!callback()) {
remove()
onBackPressedDispatcher.onBackPressed()
}
}
}
)
}
// Fragment extension
fun Fragment.onBackButtonPressed(callback: (() -> Boolean)) {
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (!callback()) {
remove()
requireActivity().onBackPressedDispatcher.onBackPressed()
}
}
}
)
}
Usage:
// In your Activity or Fragment
onBackButtonPressed {
if (shouldShowConfirmationDialog) {
showConfirmationDialog()
true // Prevent default behavior
} else {
false // Allow default behavior
}
}
Integration with Navigation Components
For apps using Jetpack Navigation, you can integrate back press handling with your navigation graph:
With Bottom Navigation
private val backPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
NavigationUI.navigateUp(navController, appBarConfig)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Setup navigation components
val appBarConfig = AppBarConfiguration.Builder(setOf(R.id.home, R.id.profile, R.id.settings)).build()
onBackPressedDispatcher.addCallback(this, backPressedCallback)
}
With Navigation Drawer
private val backPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)
} else {
NavigationUI.navigateUp(navController, drawerLayout)
}
}
}
Handling the Up Button
When using AppCompatActivity, don't forget to handle the up button in the toolbar:
override fun onSupportNavigateUp(): Boolean {
onBackPressedDispatcher.onBackPressed()
return true
}
Manual Back Press Trigger
To programmatically trigger back navigation (e.g., from a custom button):
backButton.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
Platform-Specific Implementation (API 33+)
WARNING
This approach is only necessary if you're not using AndroidX libraries. For most cases, the AndroidX solution is recommended for its backward compatibility.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT
) {
// Handle back press for API 33+
if (isTaskRoot) {
startActivity(Intent(this, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
} else {
// Use AndroidX implementation for older versions
onBackPressedDispatcher.addCallback(this) {
if (isTaskRoot) {
startActivity(Intent(this@MainActivity, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
}
Best Practices
- Use AndroidX Implementation: Prefer
OnBackPressedDispatcher
over the platform-specificOnBackInvokedCallback
for better compatibility - Proper Lifecycle Management: Ensure callbacks are tied to the appropriate lifecycle owner to prevent memory leaks
- Conditional Removal: Use
remove()
on the callback when you want to revert to default behavior - Test Thoroughly: Test your back navigation across different Android versions and navigation patterns
Common Pitfalls
DANGER
Avoid overriding onBackPressed()
when using OnBackPressedDispatcher
as it will prevent the dispatcher callbacks from firing.
// ❌ Don't do this when using dispatcher callbacks
override fun onBackPressed() {
// This will override all dispatcher callbacks
super.onBackPressed()
}
Migration Example
Converting from the deprecated approach:
// ❌ Deprecated
override fun onBackPressed() {
if (isTaskRoot) {
startActivity(Intent(mContext, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
// ✅ Modern approach
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
onBackPressedDispatcher.addCallback(this) {
if (isTaskRoot) {
startActivity(Intent(this@MainActivity, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
}
Conclusion
The migration from onBackPressed()
to the new back navigation system is straightforward with the AndroidX OnBackPressedDispatcher
. This modern approach provides better flexibility, lifecycle awareness, and compatibility across Android versions. By using the patterns and examples provided, you can effectively handle back navigation in your Android applications while following current best practices.