Saturday, April 16, 2016

Fragment Navigation in Android. The ultimate guide

Fragments are components designed to represent a portion of the behavior of an Activity; it means that it's the graphic representation of what is happening in your app. Instead of loading the Activity with widgets, views and buttons, a fragment is loaded containing all of those graphics and fancy objects, when the activity connects to a database it should display the results or the connection status to the user, when it's downloading data it should change the view so the user knows that something is happening and so on with the help of the fragments.

The main idea of the fragment is to show the information that the user needs and let the activity do the logic the application needs.

Fragment Navigation


As the small intro suggest, a well maintained application should use fragments to show information to the user using a coherent navigation system. If you still use layouts over your Activity take a look on adding fragments and how do they attach.

Adding a fragment explained

The fragment has 2 main components: the XML resource that describes the view and the logical part, the Java code.

The xml resource is the visual, it's what we want the user to see. This layout is attached to the fragment when it's created.

After the XML attaches to the logical part, the fragment itself attaches to the Activity containing both visual and logical part.

Caption this


When it's needed, the fragment can update the contents of its own layout to display the information you need from the methods that a fragment class has, but the navigation is not so simple.

To attach a fragment, the Holder Activity has to use a Fragment Manager and call for the add or replace methods to paint a fragment.

...
//This will add a fragment 
getFragmentManager().add(...).commit();
... 
//This also adds a fragment
getFragmentManager().replace(...).commit();
...

With the help of the fragment manager, you can add or replace a fragment.

Add a fragment

When adding a fragment, the add() method from the Fragment Manager adds a fragment to the Activity layout. As you may know, you need to provide the layout id where its going to be placed. The important thing here is that the fragment will stack on this layout no matter if there was already a fragment on it. Because of that, you should use add() for the very first fragment in your app, your Home Screen if you will. Since it won't stop any previous fragment running, this method will keep all of the fragments brought to the layout alive, consuming more memory than replacing them.

Replace a fragment

When a fragment is replaced with replace() it will stop any fragment already running in the given layout and it will then add the new fragment. This is ideal when moving to a new screen or step.

Now that you understand add() and replace() you can implement your navigation design with a new method from the Fragment Manager: the addToBackStack().

Backstack Trace

The BackStackTrace is a trace where the Fragment Manager keeps a record of the fragments that an Activity attaches. This is extremely important to understand because since you are working with a single Activity your app will exit if you press the Back button in Android or navigate to a previous Activity if there is one. This means that you could guide the user to a 3 different fragments inside your app, but when he / she press the back button he / she would expect to see the previous screen but instead the app will exit.

With the stack trace, the Fragment Manager will catch the back event and it will dettach the current fragment and will attach the lastest fragment added to the stack preventing the Activity to stop instead. This will always happen if there is at least 1 remaining fragment in the stack. When there are no fragments in the stack the Activity will handle the back event.

Add a fragment to the stack

Using the Fragment Manager you can call addToBackStack() before adding the fragement.



...
//This will add a fragment and will add it to the backstack trace
getFragmentManager().add(...).addToBackStack().commit();
... 
//This will replace a fragment and will add it to the backstack trace
getFragmentManager().replace().addToBackStack().commit();
...

Now don't get confused. The fragment that makes it into the stack is the current fragment, which makes sense because when you press back in the new fragment, you will go back to the previous one.

This means that the list in the backstack has a first in, last out method; when a fragment adds it will move all of the already saved fragments and will stay on top. When the back button is pressed the trace "pops" and the lastest to enter will be the first one to show. Take a look to the following example.

Activity starts from the begginig. Let's add a fragment.

...
//Remember, use add() only for the first fragment
getFragmentManager().add(...).commit();
//No backstack trace yet, press back from the home screen and the activity ends 
...



Let's move to a new screen by adding a new fragment

//Remember, use replace() to move between fragments
getFragmentManager().replace(...).
    addToBackStack(null).commit(); //Don't worry with the null parameter, it will work just fine like that
//The current fragment is now the only element in the backstack.
...

Now the first fragment is in the backstack and the second fragment is on screen


As the code says, the current fragment is stopped and replaced with the new one but its trace is saved in the backstack. This means that when the user presses back, the old fragment will show up replacing the new fragment.

When the user press "Back" the backstack will "pop" it's lastest element and put it into the screen


Let's pretend that the user never presses back and it moves from the second fragment to a third one.

//Remember, use replace() to move between fragments
getFragmentManager().replace(...).
    addToBackStack(getFragmentManager().getFragment().getTag()).commit();
//There are now 2 fragments in the backstack.
...




This means that when the user presses back from the third fragment, the stack will pop and bring the second fragment to screen, now there will be remaing the first fragment in the backstack. As you could guess the user could move from this point (the second fragment) again to a new fragment, the remaining trace in the backstack will move 1 step deeper and the current fragment will leave a trace in the backstack on the top of it. If the user goes back, it will go again to the second fragment and the first fragment will remain in the trace.

Adding complexity to the backstack

Now that you understand how does the backstack works, you could implement a much more complex design. For example, what if you want to skip a fragment between 2 when going back? Start from A, go to B, navigate to C and go back from C to A.

1) Add fragment A and don't add it to the stack trace (There is nothing behind it, the app should exit when back is pressed)

getFragmentManager().add(...).commit();

2) Replace fragment A with fragment B but now add to the backstack trace. Remember, you are adding rhe current frag,ment, not the new one.

getFragmentManager().replace(...).
    addToBackStack(getFragmentManager().getFragment().getTag()).commit();

3) Replace fragment B with fragment C. Don't add anything else to the stack.

getFragmentManager().replace()..commit();


Now, from fragment C, when going back, the fragment manager goes to the backstack trace and "pops" the lastest fragment on it. As you could imagine, the only fragment there is fragment A therefore you would go to fragment A instead of fragment B.


Second fragment is skipped because there was no such trace on the backstack

You could also clear the entire stack and add a new one. Like when the user ends some steps for a configuration and you want to take him / her to the Home Screen without having the chance to going back to all of those already used screens.

One thing you could try is to manually pop all of the fragments in the backstack trace.

for (int i=0; i<getFragmentManager().getBackStackEntryCount(); ++i){
    getFragmentManager().popBackStack();
}

And after add the fragment like it was the very first one added (No backstack trace added).

Like this link on stackoverflow.com suggest, you can also do the followin for the same result

getFragmentManager().popBackStack(String name, FragmentManager.POP_BACK_STACK_INCLUSIVE);


Now you know all you need to create an effective fragment navigation design. If you have any doubt or you want to share an idea on this topic please feel free to do it and share it with the rest of developers.

Than you for reading


No comments:

Post a Comment