This is the fourth part of a 6 posts series on Fragment oriented application architecture. In the previous post I talked about Inter-Fragment Communication. In this part I am going discuss about elegantly handling back button press inside fragments in a fragment oriented application.

Android devicesÂ have a hardware back button which normally serves the purpose of going back through the screens/actions stack. Callback to a back button press event is received in the foreground Activity ( onBackPressed() event callback) which can be overridden and handled.

// GENERIC ACTIVITY LEVEL BACK PRESS HANDLING @Override public void onBackPressed() { if(some condition) { // do something } else { super.onBackPressed(); } }

But fragments do not get notified of a back press event. Suppose an Activity has a stack of 3 fragments. For a regular android user, normal expectation upon a back press would be to navigate back through the fragments. Of course, we would have added all the transactions to BackStack and now can enjoy the privilegeÂ of using popBackStack() method, but each fragment may have one or more special situations to consume back press. Handling back press for each of fragments inside the onBackPressed() call back of activity is the obvious choice but if the activity is supposed to host a good number of fragments, itsÂ onBackPressed() callback is likely to get cluttered and messed up.

For instance, in a ListFragment, a list row might expand upon clicking and a subsequent back press requires to collapse the expandedÂ row.

// A SAMPLE CLUTTERED onBackPressed() CALLBACK IN AN ACTIVITY // WHICH MAINTAINS A REFERENCE TO THE CURRENTLY ACTIVE FRAGMENT. @Override public void onBackPressed() { if(selectedFragment.equals(fragmentA) && fragmentA.hasExpandedRow()) { fragmentA.collapseRow(); } else if(selectedFragment.equals(fragmentA) && fragmentA.isShowingLoginView()) { fragmentA.hideLoginView(); } else if(selectedFragment.equals(fragmentA)) { popBackStack(); } else if(selectedFragment.equals(fragmentB) && fragmentB.hasCondition1()) { fragmentB.reverseCondition1(); } else if(selectedFragment.equals(fragmentB) && fragmentB.hasCondition2()) { fragmentB.reverseCondition2(); } else if(selectedFragment.equals(fragmentB)) { popBackStack(); } else { // handle by activity super.onBackPressed(); } }

This mess would grow exponentially with an increase in number of Fragments and complexity of each of them. Not clean!

After hours of hit and trials, I’ve finally come up withÂ a strategy to cleanly propagate the back press event down to fragments, and let activity handle it only if the event doesn’t get consumed at fragment level. Enters theÂ BackHandledFragment! An abstract base class that conveniently provisions to let a child active fragment utilize back press events. Host activity keeps track of its active fragment and in its onBackPressed() callback, it first checks if the active fragment requires to consume the back press, before handling it by itself.

// BASIC IMPLEMENTATION OF BACK HANDLED FRAGMENT public abstract class BackHandledFragment extends Fragment { protected BackHandlerInterface backHandlerInterface; public abstract String getTagText(); public abstract boolean onBackPressed(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if(!(getActivity() instanceof BackHandlerInterface)) { throw new ClassCastException("Hosting activity must implement BackHandlerInterface"); } else { backHandlerInterface = (BackHandlerInterface) getActivity(); } } @Override public void onStart() { super.onStart(); // Mark this fragment as the selected Fragment. backHandlerInterface.setSelectedFragment(this); } public interface BackHandlerInterface { public void setSelectedFragment(BackHandledFragment backHandledFragment); } }

This class declares anÂ abstract method onBackPressed() .Â Child fragments would override this method to consume the back press and return a boolean to tellÂ if back-press event was consumed.

onCreate() callback is enforcing the implementation of BackHandlerInterface, which has a method to mark a Fragment as the selected (or active) fragment of the hosting Activity. onStart() calls this communicator method to update selected fragment to current fragment (using this keyword).

Following would be the resulting back handling related code in the activity.

// BASIC ACTIVITY CODE THAT LETS ITS FRAGMENT UTILIZE onBackPress EVENTS // IN AN ADAPTIVE AND ORGANIZED PATTERN USING BackHandledFragment public class TheActivity extends FragmentActivity implements BackHandlerInterface { private BackHandledFragment selectedFragment; @Override public void onBackPressed() { if(selectedFragment == null || !selectedFragment.onBackPressed()) { // Selected fragment did not consume the back press event. super.onBackPressed(); } } @Override public void setSelectedFragment(BackHandledFragment selectedFragment) { this.selectedFragment = selectedFragment; } }

That’s it for back press handling!

Sample app is excessively using this method. BaseFragment in sample appÂ is based on the aboveÂ basic BackHandledFragment, thus, its inherited fragments are capable of handling back press events.

// GENERIC PATTERN OF OVERRIDING onBackPressed() METHOD // INSIDE A FRAGMENT THAT EXTENDS BackHandledFragment. @Override public boolean onBackPressed() { if(condition 1) { // do something; return true; // event consumed } else if(condition 2) { // do something else; return true; // event consumed } else { // event not consumed, let Activity handle it. return false; } }

In the next post Iâ€™m going to talk about Centrally Managing Sessions in a Fragment oriented application, taking Facebook as an example.