Simplified Fragment Navigation, using a custom backstack and commitNow()

Anyone who’s been following my non-Realm-related write-ups is most likely confused about this one right now.

Me? An advocate of custom viewgroups, previously using a modified version of square/flow, previous article about square/coordinators; and yet, here I am, writing about FRAGMENTS?

Yep! I must confess: my true nemesis is not Fragments themselves, despite their quirky lifecycle.

The real enemies are the Activity backstack and task navigation, and the Fragment backstack with its tagged transaction system.

Which is why instead, I’ve integrated Fragments with my own backstack library, to allow simpler navigation. But you’ll see that in a bit.

Why is the Activity backstack bad?

I’m more-so an advocate of single Activity applications than of custom viewgroups. Activities have a fairly intuitive lifecycle on their own.

from http://stackoverflow.com/a/24157802/2413303

But the moment you have more than one of them, it becomes a mess:

Now you need to manage the first Activity which is in the background, but its view hierarchy still exists!

This is lots and lots of overhead with startActivityForResult(), or registering to an event bus in onCreate() and unregistering in onDestroy(), just trying to keep background “stale view hierarchies in sync”.

This is no good. We ought to keep alive only what needs to exist. Not to mention that in order to create a new Activity, you need a reference to a Context (generally an Activity context!), and you need to use an Intent — our application state is implicitly bound to the Android framework.

Why is the Fragment backstack bad?

Look up fragment backstack doesn’t work site:stackoverflow.com. Lots and lots of results just for that.

However, the real issue with it is that it doesn’t let you name the fragment itself that you’re adding, it stores the transaction itself.

from http://hahack.com/wiki/android-fragment.html

This makes backstack management rather convoluted. This is not a problem as long as you use the typical combination fragmentTransaction.replace(R.id.container, new MyFragment()).addToBackStack(null).commit() and navigate only forward and backward, but any complicated operation becomes either difficult, or impossible.. What if I wanted to go from [A,B,C] to [B,D]?

Also, commitNow() disallows adding to the FragmentManager’s backstack. It’s quite clearly in the code:

@Override
public void commitNow() {
disallowAddToBackStack();
mManager.execSingleAction(this, false);
}

What people forgot to tell you about the FragmentManager

Apparently, the FragmentManager and FragmentTransaction have a few convenient methods, more than just your typical add and replace you see in every guide.

So, we have remove() that removes a fragment completely, and also two handy methods — detach and attach, which destroy the view hierarchy, but retain the fragment state.

Compared to the Activity stack, this is exactly what we were looking for!

Managing a backstack of keys that identify Fragments

Our weapon of choice

We would like to manipulate our backstack with simple operations, just like with Flow:

Flow history manipulation

But to retain a simpler API with less baggage, I’m going to use my new library called simple-stack.

Simple-Stack history manipulation

Identifying the Fragments in our custom backstack

Just like with custom viewgroups, we can associate the Fragment with a Key.

This Key needs to know the Fragment’s tag, and needs to be able to instantiate the Fragment when necessary.

public interface Key extends Parcelable {
Fragment newFragment();
String getFragmentTag();
}

We can associate the Fragment with its Key quite easily, by placing it into the Fragment’s bundle.

Any arguments to the Fragment can be part of the Key itself. That way it can pass them to the fragment’s static factory method, and add it to its arguments bundle.

We can also keep the key inside the Fragment’s view hierarchy if we so desire.

Considering we use @AutoValue for our keys, the generated toString() is sufficient for providing a TAG for our specific Fragment inside the FragmentManager in order to uniquely identify it.

Managing the Fragments inside the FragmentManager according to the currently active keys

Simple-Stack requires a StateChanger implementation to handle a state change from state[A] to state[B], for example from [A,B] to [A,B,C]. — analogously to the Dispatcher in Flow.

In our case, we can now identify any Fragment using a key, we can create a Fragment in case it does not exist, and we have simple backstack operators. Now all we need is to tell the FragmentManager what to do in case of any particular state change.

In terms of pseudo-code, what we need to do is this:

begin fragment transaction
disallow adding to backstack (commitNow would not work otherwise)
animate the fragments according to state change direction
for all keys in the previous state
if the fragment associated with key exists
then if it is not found in the new state, remove it
otherwise if it is not yet detached, then detach it
for all keys in the new state
if the fragment is the new active state
then if it exists but is detached, then attach it
else if it does not exist, then create and add it
if it is not the new active state,
then if it exists and is not yet detached, then detach it
commit fragment transaction now

This might look a bit complicated in code, but it really isn’t.

commitNow() is a fairly new method, added in Support Library 24.0.0 to support adding fragments to the FragmentManager synchronously, but as mentioned above, you cannot use the FragmentManager’s backstack with it. You can however use your own.

And with this particular setup, if our application is single-page (no master-detail flow), this State Changer can handle ANY fragment-based setup, and allow the custom backstack to work with it — surviving both configuration change and process death, of course.

Some examples

Who would have thought navigation to a particular screen could be so simple, right?

Contrast that with opening a fragment transaction each time, and having to use the fragment manager for it directly. Or creating intents and starting Activities in random places in your app, being confined to use an Activity context, no matter where you are.

Conclusion

We’ve successfully integrated Fragments with a custom backstack, while retaining most benefits of our previous, custom view-based approaches.

Animation using fragments is a bit different, and we get a lifecycle out of the box; but we still have complete control over the backstack.

And did I mention this backstack of keys is technically independent from the Android framework, meaning you can keep the navigation via backstack manipulation inside the presenter layer? Sweet!

Coordinators example rewritten with Fragments

And most importantly, I’ve retailored the original MVP example to use Fragments using the navigation explained in this article, and is available in the linked repository.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.