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.

(Sample application’s source code and README)

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.

Share this:

Privacy Preference Center