The last edition of Android Programming: The Big Nerd Ranch Guide was released in October 2019. A lot has changed since then. To help...
An Investigation into Flow and Mortar
Fragments have started to garner a negative reputation in the Android community, despite being officially supported by the Android team at Google, and there are a host of reasons why:
- We have to rely solely on default constructors, rather than custom constructors.
- Nested Fragments are prone to bugs and limitations.
- The Fragment lifecycle is complex.
But perhaps most importantly, Android developers simply don’t seem to enjoy using Fragments, viewing them more as a hindrance than as a help.
However, we encourage a mindset of constant exploration and continual learning, so I began investigating the most popular alternatives to Fragments.
Enter Flow and Mortar
About a year ago, Square introduced two new libraries called Flow and Mortar, with the noble goal of ridding the Android world of Fragments forever. Square has a stellar reputation in the open-source community, having built such amazing libraries as:
Suffice it to say, I trust them. I figure that any resource from Square is probably useful (or, at the very least, interesting), so it’s worth giving it a look.
I should note before diving in that Square uses these libraries only on a few internal projects, and they’re still in pre-release stages as of this writing. That said, both libraries have seen active development in recent months, which bodes well for their respective futures. While the libraries might be shifting sands that could change, break or die at any moment, the core architectural principles underlying them should remain constant.
First, let’s go over architecture in Android applications. In the days before Android Honeycomb, before Fragments even existed, the standard pattern for creating Android apps was to create a bunch of Activities. Typically, most developers loosely followed a Model-View-Controller paradigm, which made a lot of sense. The model was the underlying data, typically some database or maybe a web request stored as JSON, or any various enumerations of Java objects. The View would happily be the XML layouts that the developer would craft for each Activity, and the Controller was the Activity associated with each screen of the application.
This is a simplistic overview, but you can see how Android fits into MVC by default.
With the advent of Fragments in Honeycomb, the Android team made it easier to deal with different form factors. Now, the standard architecture became a few Activities and a bunch of composed Fragments, so that all of our apps would be cross-platform between phones, tablets, watches or spaceships.
At the time, this seemed like a boon. Fragments were all the rage, and decomposing an Activity into multiple fragments was great. Plus, the architecture still adheres to MVC, though Activities were often reduced to merely being Fragment holders and the interface between Fragments and the OS.
But what if Activities weren’t properly given a chance? Maybe, if used in conjunction with custom Views, Activities don’t actually need the support of Fragments to create cross-platform modular apps. The core idea behind using Flow and Mortar is to explore this question. Flow and Mortar work by replacing our Fragments with custom Views paired with dedicated and injected controller objects, so we can work solely with Views instead of Fragments.
We’ll be constructing the middle portion of this diagram over the course of our discussion, figuring out where to put different pieces of the puzzle in a world without Fragments. We’ll watch as the architecture evolves from standard MVC into something altogether different, which (spoiler alert) will heavily involve Flow and Mortar.
So, what, exactly are Flow and Mortar, and how can they help?
Flow divides an application into a logical collection of “Screens.” Screens are not any sort of special library object, but rather a plain old Java object (POJO) that we create to represent sections of our application. Each Screen is a self-contained segment of the app, with its own function and purpose. A Screen’s usage isn’t so different from the typical Activity’s usage. Each Screen corresponds to a particular location in the app, sort of like a URL to a webpage or a really specific Implicit Intent in Android. Thus, the Screen class serves as a self-contained readable definition of a section of the application.
Each Activity in our application will own a “Flow” object. The Flow object holds the history of Screens in a backstack, similar to the Activity backstack or the FragmentManager’s backstack of Fragments, allowing us to navigate between Screens by simply instantiating where we want to go. Instead of an application having a bunch of Activities, there exists a small number (ideally one) Activity that hosts multiple Screens. It looks something like this:
If we want to go to a new Screen, we simply instantiate it and tell our Flow object to take us there. In addition, Flow comes with
goUp() methods, which behave as expected. While the introduction of
goto statements in Java might scare some folk, it’s not so terribly evil.
In essence, Flow tells us where to go in our app. The advantage of using Flow is that it gives us a simple way to navigate around various custom Views that we’ve defined (i.e., Screens), freeing us to not worry about Activities or Fragments—we deal solely with Views. It creates a simpler, cleaner, View-centric world.
Mortar is a library focused around Dagger and its associated dependency injection. Mortar divides an application into composable modules using a few distinct parts: Blueprints, Presenters and a boatload of custom Views.
Each section of a Mortar app (that is, each Screen, since we’re also using Flow) is defined by a Blueprint, which is given its own personal Dagger module. It ends up looking a bit like this:
Flow and Mortar play well together. All we have to do is adjust our Screen class to also implement the Blueprint interface (provided by Mortar), and it gives us the Dagger scoping for free.
The Presenter is a singleton object that functions as a view-controller with a simple lifecycle and persistence bundle. Each View has an associated Presenter, which lives inside the associated Screen (with a Blueprint). Since the Presenter is scoped to just the Screen that it lives in, the Presenter (our heavy-duty controller object) will be garbage collected if we go to a new Screen using Flow. The Dagger scoping of Mortar combined with automatic garbage collection allow our app to be more memory efficient, since any of our controller objects that aren’t currently being used are GC-ed away. In Activity-land, there are fewer guarantees of garbage collection when switching between Fragments and Activities.
Custom Views are used liberally so that we can simply inject all of the important model data through Dagger, and then use the associated Presenter to control the View itself. Presenters survive configuration changes, but have enough Activity lifecycle knowledge to be restored after process death. The Presenter actually hooks into the Activity’s
onSavedInstanceState() bundle, using the same mechanism for saving and loading data on configuration change as an Activity would.
The Presenter life cycle is a simple one with only four callbacks:
Not nearly as confusing a lifecycle as Fragments, if I do say so myself!
There are a lot of moving parts and new terms and classes and all sorts of room for confusion. So in sum, we have the following pieces of the puzzle:
- Screen: A particular location in the application’s navigation hierarchy
- Blueprint: A section of an application with its own Dagger module
- Presenter: A View-controller object
- Custom Views: Views defined by Java and usually some XML
Here’s what our final Mortar and Flow architecture looks like:
Instead of sticking with Model View Controller, the architecture has morphed into more of a Model View Presenter style. The big difference concerns the handling of runtime configuration changes like rotation. In MVC, our Controller (Activities and Fragments) will be destroyed alongside our Views, whereas in MVP, only our View will be destroyed and recreated. Nifty.
With all of this work and redesign, there needs to be some payoff, so let’s talk about the good things.
- Using Mortar and Flow forces us to create a modular architecture with a Model View Presenter design, which is useful for maintaining a clean codebase.
- Testing becomes easier through the dependency injection of our custom views and Presenters.
- Animation can be dealt with on a View level, as opposed to worrying about Fragment and Activities as much.
- Mortar scoping means the application will be more memory efficient, with garbage collection occurring on the View and Presenter level automatically.
And, of course, we no longer have to worry about Fragments and their various quirks.
Room for Improvement
Flow and Mortar do have some issues:
There is a steep learning curve. The patterns are complex and require a lot of exploration and experimentation before you can really understand them, and these libraries are certainly not for beginners. Even for an expert, there is a steep design pattern investment.
If you’re going to use Mortar and Flow, you really have to go all the way with it. It would be difficult to interplay “standard” Android with the Fragment-less Android style. If you want to convert an existing project to Mortar and Flow, the process, while certainly possible, will be long and arduous.
There’s a mountain of boilerplate and configuration to deal with. This is my biggest complaint. With all of these new classes and interfaces, I often felt like I was drowning in uninteresting code that was required solely to hook everything together, which just isn’t as much fun.
Both Mortar and Flow are in the pre-release stages of their development, with no official release in sight. This means dealing with issues and changes and updates, with the libraries shifting from beneath us, but this also means there is plenty of time for improvement.
Working with Mortar and Flow was a fun experiment. I enjoyed trying out some new libraries and seeking alternatives to the standard Fragment-oriented architecture, but I don’t think Mortar and Flow are the solutions the Android world are seeking. That could change in a few months or years. I hope that these projects will garner more attention and love and continue to improve, and I’ll definitely be keeping an eye on them.