android

Android Tutorial: Drawer and Fragment navigation made easy(ier….)

Hey, this time I want to write a bit about the Drawer design pattern in Android. If you have played with this I’m sure you have faced one or more issues regarding navigation (and the fragment’s back stack). I believe this tend to happen because we are used to let Android navigate those view stacks but when you transition to fragment transactions you are forced to pay attention to this.

You will find the code for this blog post here:

https://github.com/aarcoraci/fragment-navigation-simple

Note: this article has a “part 2” that improves on this code. You should still go through this post first.

Part 2:  Android Drawer and Fragment Navigation. A more “real life” scenario

The first two things I would like to recommend are:

  • Don’t be afraid of the fragment’s back stack, but be respectful of it 🙂
  • PLAN your app workflow and screens very carefully. I can tell you I’ve learned this the hard way

That besides, I’ll propose a very simple app for this example. To make it simple, I’ll show you the view diagrams or workflow:

app_views.png

Pretty simple: we have a home fragment that can have some options, buttons or whatever. From the drawer you can access two fragments: Categories and Users. From Categories you could see the details of an item displayed there.

This tutorial is about navigation: for this example I’m defining the home fragment as my ROOT fragment (this is a tree diagram after all) and we have two branches: Categories and Users. Once you have reached one of those, pressing the back key should return to Home. Pressing the back key again should exit the app.

All this sounds simple enough but it’s not always that easy as it appears to be. To achieve this in a clean fashion we will use a combination of techniques: playing with the Fragment’s back stack and the Action Bar to give our user’s a hint they are deep in our app. Lets complete the diagram with some helper images:

app_views_bar.png

I have added some images on how I would like to see the navigation bar: only from the ROOT you see your hamburger icon. Once the app is navigated, the back arrow is shown. You may be wandering what if you wan’t to have the drawer accesible from every section of your app. That’s entirely possible but exceeds this tutorial and it can will be really messy.

Let’s get some code here. First I would recommend creating a base class for your fragments. It will help you have some reusable code that will become handy later, specially if you are going to be doing Async Tasks or if you need access to shared resources. Here is an example of a very simple base Fragment:


public abstract class MyAppFragment extends Fragment {
 /**
 * get a unique string identifier for this fragment. Can be used as a key to add
 * into the back stack
 *
 * @return unique tag
 */
 public String getFragmentTag() {
 return this.getClass().getSimpleName();
 }

/**
 * obtain the current instance of the activity holding this fragment
 * @return main activity instance
 */
 public MainActivity getMainActivity() {
 return (MainActivity) getActivity();
 }
}

This is a very simple class but will help us maintain our code organized and read-friendly. I’ve also added a very simple Factory Pattern to get the correct Fragment:


public class MyAppFragmentFactory {

    public static MyAppFragment getFragment(AppSection section) {
        switch (section) {
            case HOME:
                return new FragmentHome();
            case USERS:
                return new FragmentUsers();
            case CATEGORIES:
                return new FragmentCategories();
            case ITEMS:
                return new FragmentItem();
            default:
                return null;
        }
    }
}

Simple enough: we get a Fragment based on an Enum parameter. Now we will put all this together from our MainActivity. What I have done it’s take the Drawer Sample Activity from Android’s Studio wizard. I replaced the <include> tag for a container suitable for inserting our fragments there.

The back stack:

Fragment’s that don’t get added to the back stack will not respond to the “on back press” event. Let’s imagine your app starts and you insert your first fragment via a transaction (in this example the Home fragment). If you add this to the back stack it means you require an additional “back key press” to exit your app. You wan’t to avoid it since your Home fragment is the Root your app. The following method will handle fragment transactions globally (from your MainActivity) and will be flexible enough to offer this functionality:

/**
 * Navigates to a fragment within the app
 *
 * @param section    where to go
 * @param addToStack should this transaction be added to the back stack ?
 */
public void navigateToSection(AppSection section, boolean addToStack) {
    // obtain the fragment we want to navigate to
    MyAppFragment fragment = MyAppFragmentFactory.getFragment(section);

    FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
    fragmentTransaction.replace(R.id.main_content, fragment, fragment.getFragmentTag());
    if (addToStack)
        fragmentTransaction.addToBackStack(fragment.getFragmentTag());
    fragmentTransaction.commit();
}

Now we need to change our Application Bar, not only to show the changes visually but to react to them as well. A very simple approach is the next: if  your back stack is empty, you allow the users to interact with the Drawer, if not you offer the “Back” arrow functionality. You can achieve this very easily by listening to the changes on the fragment’s back stack (via the FragmentManager.OnBackStackChangedListener interface). Once you do that, you can use the following code:

On the OnCreate method set everything up:

// let the fragment manager know that MainActivity will be listening to events
getSupportFragmentManager().addOnBackStackChangedListener(this);

/*
 AppBar setup
 */
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
toggle.setToolbarNavigationClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (getSupportFragmentManager().getBackStackEntryCount() == 0)
            onBackPressed();
    }
});
/*
    app should start on the home section
    notice we won't be adding it to the back stack. Pressing back
    should exist the app
 */
navigateToSection(AppSection.HOME, false);

And then implement your listener method:

@Override
public void onBackStackChanged() {
    int totalFragmentsInBackstack = getSupportFragmentManager().getBackStackEntryCount();
    // if there are not more entries on the back stack it means we are in the root. Drawer should be visible
    if (totalFragmentsInBackstack == 0) {
        getSupportActionBar().setDisplayHomeAsUpEnabled(false);
        toggle.setDrawerIndicatorEnabled(true);
        toggle.syncState();
        drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
    } else {
        // else show the back arrow function. NOTICE: order of these methods is IMPORTANT
        toggle.setDrawerIndicatorEnabled(false);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
    }
}

All we are doing is checking if we are on the ROOT or not. Based on that we change our AppBar to mimic our desired workflow.

From here on things can get more complicated, specially if you want to include the drawer from every screen (really think this thru and see if you find samples of this on the play store, and I mean good samples).

I’m leaving the code for the working solution here, feel free to ask questions and suggest editions or corrections.

https://github.com/aarcoraci/fragment-navigation-simple

Advertisements

2 thoughts on “Android Tutorial: Drawer and Fragment navigation made easy(ier….)”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s