Towards a fragmentless world: Creating a Flow-like custom backstack (Part 1)

People have been trying to get rid of fragments for quite a while now. But many are afraid to use a custom solution (like Conductor, Scoop, Pilot, Triad, and Flow - or my own fork of it: Flowless) , separate from what the Android Framework already provides you (Activities, primarily), or what the support library provides you (Fragments).

Of course, the support library was created so that developers would be able to use Fragments even when it wasn’t yet available. However, for us, this means that there is no real magic in how fragments work. It’s just code. We could easily create something like it. Something better. Or just simpler.

In this series of articles, I intend to create a (proof-of-concept) backstack implementation from the ground up — albeit heavily inspired by how Flow works, and showing step by step what problems arise and how to solve them.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Creating the Backstack

State representation: ArrayList<Parcelable>

First, we need to represent where we are in the application. And we must be able to persist this across configuration change and process death without too much trouble. Therefore, what we need is a List, and each element in it is a Parcelable.

With this setup, we can easily persist the backstack using bundle.putParcelableArrayList("BACKSTACK", new ArrayList<>(backstack.getHistory())).

Operations and backstack manipulation: goTo, goBack, setHistory

Similarly to latest backstack implementations, we want to be able to support the following operations:

  • goTo(Parcelable key): add element to the backstack if it does not exist, otherwise navigate back to it
  • goBack(): navigate back on the stack if possible, otherwise don’t handle the event
  • setHistory(List<Parcelable> newHistory, Direction direction): set up a new history stack, with a specified navigation direction (back, forward, or replace).

Manipulating the history: StateChange

When we change the state from A to B, we must know that we were in A, and that we are going to B.

Or more specifically, that when we’re going from [A, B, C] to[D], we must know both sides of the state change. And we need to know if we’re going forward, or backward (or if we’re just replacing the history altogether).

For this, we need a class that represents the state change — this case, called StateChange.

We create a StateChange whenever the state is changed in the backstack.

The StateChanger is just an interface which receives the StateChange, and the Callback which signals the completion.

— — — — — — — — — — — — — — — — — — — — — — — — — — —

Initializing the backstack

The Backstack should be created with the initial history, so that on initialization, the StateChanger can set up the initial state.

Setting up initial parameters

The initial parameters are just constructors for the backstack.

Setting up the “initialize” state change

…or as it’s called in Flow, the bootstrap traversal.

This is the first initial call to the StateChanger when it’s set to the Backstack.

In Flow, this is a traversal as {null, [initialState]}, in this case it will be {[], [initialState]}.

So in reality, we must adjust the changeState() method a bit, to handle the initializing state change as a special case — where the previous state is an empty list.

With that, we can actually integrate the Backstack into our Activity, set up the Activity as a StateChanger, and we’re pretty much good to go!

— — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Integrating the Backstack into the Activity

We must set up the following:

  • create initial keys if no previous persisted stack exists
  • persist Backstack across config change (via retainCustomNonConfigurationInstance())
  • persist Backstack across process death (via onSaveInstanceState())
  • set Activity as the state changer
  • handle back press

And this is done in the following gist.

With this, we pretty much have our custom backstack implementation that actually works! Well, kind of. It still has some quirks that need to be addressed. But at a first glance, it works perfectly fine!

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

What’s next?

For the next part of the series, I intend to look into:

The original source code and repository for the gists can be found here.