I would like to implement the OnBackPressedCallback detailed in Navigation Components in my app. The documentation is very clear on how to add this custom behavior in fragments, and works quite well. After reading the linked documentation, it states that you should avoid overriding onBackPressed in your activity. My current onBackPressed method looks something like this:
@Override
public void onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
} else if (shouldOverrideBackPressed()) {
navigationController.popBackStack(R.id.main_fragment, false);
} else {
super.onBackPressed();
}
}
The last line super.onBackPressed(); is what I'm not clear about. How do I retain the default behavior for the back button in an activity while implementing the OnBackPressedCallback? This is my current implementation:
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
} else if (shouldOverrideBackPressed()) {
navigationController.popBackStack(R.id.main_fragment, false);
} else {
setEnabled(false);
MainActivity.this.onBackPressed();
}
}
});
If I don't include the setEnabled(false); line, then I get a Stack Overflow error, which makes sense. But surely there is a more elegant way to provide default behavior?
You shouldn't be putting all of that logic in one OnBackPressedCallback. Instead, each case should be its own callback that is only enabled when it is the one that should be handling the back pressed.
You should not be calling onBackPressed(), etc in handleOnBackPressed(). An important part of the contract is that when you get a callback to handleOnBackPressed(), you must handle the back pressed button there. That's precisely why you have control over exactly when the callback is enabled or not.
This means you should create one OnBackPressedCallback for your override behavior (although you should probably not being doing that with Navigation anyways) and another for the DrawerLayout that uses a DrawerListener for enabling and disabling the callback:
OnBackPressedDispatcher dispatcher = getOnBackPressedDispatcher();
final OnBackPressedCallback overrideCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
navigationController.popBackStack(R.id.main_fragment, false);
}
};
// Whenever your `shouldOverrideBackPressed()` changes, you need to
// call setEnabled(true) or setEnabled(false) so that callback
// is only called exactly when it needs to handle the back button
// Add this one first since they are called in reverse order
dispatcher.addCallback(this, overrideCallback);
// Now set up the DrawerLayout's callback
final DrawerLayout drawerLayout = findViewById(R.id.your_drawer_layout);
final OnBackPressedCallback drawerCallback = new OnBackPressedCallback(
drawerLayout.isDrawerOpen(GravityCompat.START)) {
@Override
public void handleOnBackPressed() {
// Unconditionally close the drawer when it is open
drawerLayout.closeDrawer(GravityCompat.START);
}
};
// Now add a listener so that this callback is only
// enabled when the drawer is open
drawerLayout.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
@Override
public void onDrawerClosed(View drawerView) {
// Assume you only have one drawer
drawerCallback.setEnabled(false);
}
@Override
public void onDrawerOpened(View drawerView) {
// Assume you only have one drawer
drawerCallback.setEnabled(true);
}
});
// Add it last so that it gets called before the overrideCallback
dispatcher.addCallback(this, drawerCallback);
Of course, if you're overriding things at the Activity level, there's no downside to keeping your logic in onBackPressed() - as per the documentation, any callbacks registered from Fragments, etc. will correctly be called when you call super.onBackPressed().
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With