Master-Detail Revisited: Converting Flow’s sample to Simple-Stack

Gabor Varadi
5 min readFeb 7, 2017

Back in the day, when Flow 0.9 came out, there was only one flow-sample. Well, apart from the one in the square/mortar repository, anyways. Later, flow-sample was removed from the square/flow repository.

You may ask, “what’s the point of talking about something that was deleted and forgotten since 1.5 years ago?”

There’s a pretty good reason for that, actually. It is, to date, the only master-detail implementation publicly available and open-sourced, written by Square, using Flow.

master-detail flow-sample

I’ve ported this flow-sample to use Dagger2 and Simple-Stack instead, because Simple-Stack is easier to understand than Flow 0.12 — as honestly, PathContainer never made sense.

The repository for the new source is available here.

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

Despite the title, I’m not really going to talk about the actual conversion process, it’s all available in this commit. And the terminology between Flow and Simple-Stack (for example, Dispatcher vs StateChanger) is explained fairly well in this comment.

Instead, I’m here to tell you about how this master-detail setup works.

Project architecture and components

Project structure of the flow-sample

What we have is essentially:

  • data classes ( Conversation and User)
  • Containers which are essentially custom viewgroups, which can handle a state change in a particular way — which are inflated depending on current configuration ( FramePathContainerView, MasterPathContainerView, and DetailPathContainerView — but also TabletMasterDetailRoot)
  • A state changer implementation that can swap out and add a new view, while persisting the previous view’s and restoring the new view’s state ( SimpleStateChanger, originally the SimplePathContainer)
  • custom view groups for each particular state of our app ( COnversationListView, ConversationView, FriendListView, FriendView, and MessageView)
  • custom application DemoApp,that sets up the Dagger2 component SingletonComponent
  • the main activity which hosts the backstack
  • Paths, which essentially contains every single key in our application as an inner class. For some reason. I’ve honestly never considered putting all my keys in a single file, but okay!

State representation: Path

Back in Flow 0.9 to 0.12, your state was represented by a Path. This essentially contained the Key of the application, and its ViewState.

In Flow 1.0-alpha (and in Simple-Stack), these two were detached, greatly simplifying the API. So in our case, Path is just a Parcelable with a title.

public final class Paths {
public abstract static class Path implements Parcelable {
public abstract String getTitle();
}

Beyond that, our states are all just immutable parcelable data classes generated by @AutoValue, as usual.

@Layout(R.layout.friend_view)
@AutoValue
public abstract static class Friend
extends FriendPath {
public static Friend create(int position) {
return new AutoValue_Paths_Friend(position);
}

@Override
public String getTitle() {
return "Friend";
}
}

However, what’s interesting is that most keys (except NoDetailsPath) extend from a class named MasterDetailPath, which returns its parent, and whether it is a master key.

public abstract static class MasterDetailPath
extends Path {

public abstract MasterDetailPath getMaster();

public final boolean isMaster() {
return equals(getMaster());
}
}

And with that, it describes the parent-child relationship per a given “feature set” in the application.

Master/Detail and handling orientation

Layouts

In order to handle a different view according to different orientation and device size, we can use Android’s built-in resource qualifier system to define a tablet layout for sw600dp-land, and a phone layout.

These two layouts will however display two completely different view hierarchies: one that handles portrait, and one that handles master/detail flow.

Master/Detail: Portrait Root
Master/Detail: Landscape Root

Viewgroups

This is actually where all the magic is hidden, MasterPathContainerView and DetailPathContainerView.

They are associated with a subclass of SimpleStateChanger, which is responsible for preserving/restoring state, and inflating the custom views. But it also selects what layout to inflate based on the key.

In portrait, this is based on the @Layout annotation.

In landscape, this is a bit more complicated.

  • Master Container

The master container does two things:

  • it short-circuits (says its state change is complete) if it detects that the master container already contains the view that is associated with this given master key
  • selects the master layout from the current top key
  • Detail Container

The detail container selects the Paths.NoDetails path as the source of its layout, if the current key is a master.

  • Tablet Master Detail Root

The two viewgroups mentioned above are managed by the TabletMasterDetailRoot. It holds the two container views, and delegates the state change to them — and waits for them to finish.

This is actually the most complicated class in the whole sample.

The method updateSelection is what is responsible for making sure our selected item in the master stays selected, according to the index of the selected detail — even after rotation or process death, directly from the top state.

Otherwise, it is responsible for delegating back, making sure that the right view gets the opportunity to handle it if need be. (This is actually not used in the sample.)

The most important part is that it delegates the state change to the inner containers, which can handle it as necessary.

Conclusion

With this, we can finally understand how the original square/flow-sample handled master-detail layouts.

As to whether this is less or more complicated than using only one state changer that manages the views based on current orientation inside of normal viewgroups, without nested delegation — that’s an interesting question.

One global state changer for landscape: handling full screen, master, and detail views

The repository for the old flow-sample is available here.

The new (and revamped) master-detail Flow sample is available here.

--

--

Gabor Varadi

Android dev. Zhuinden, or EpicPandaForce @ SO. Extension function fan #Kotlin, dislikes multiple Activities/Fragment backstack.