Turning Your UI Into a Well Organized Symphony With Conductor
User interfaces have evolved over time. However, the archetypal approach to creating great user interfaces on Android has not. The first party tools available to all Android developers for crafting elegant user interfaces are Activities, Fragments, and Views. For the sake of this article, we will define a custom View to be a class that extends the View class and handles the actions of the user interface component.
Why Use Fragments?
When choosing between Activities, Fragments, and Views, there are valid reasons to choose Fragments over the other options. These include:
- To provide a responsive user interface that responds to changes in device configuration(ie rotating the device or using a tablet vs phone)
- To provide a framework for transitions between user interface elements
- To create modular user interfaces
- To manage the back stack so that you can have back navigation(while also available in Activities, is not an option in using custom Views)
- They are included as a part of the Android SDK, thus no need to have any extra libraries, which would limit the number of included methods and application size.
The Trouble With Fragments
From the previous bullet points, we would be behooved to write all of our user interfaces utilizing Fragments. Initially, this was true, the majority accepted that Fragments were how we were supposed to write responsive user interfaces for Android applications. However, Fragments didn't live up to the promise of what they could have been and have had a controversial history. A few years ago there was an article from Square, found here, about how they were moving away from Fragments.
If we are not going to use Fragments then the other options are to use either multiple Activities or to use a single Activity and custom Views. Using multiple Activities unfortunately leaves us without much modularity as well as some hidden costs associated with running a new Activity for every view you create. Creating a new Activity has more overhead than creating a new Fragment. Activities are also destroyed on configuration changes(ie rotating the screen). This means your state could potentially be destroyed when a user rotates their device. This also leads to some complications in testing your user interfaces as there is more interdependent logic stored within the Activity and it's lifecycle methods. Custom Views on their own are not a viable option as they do not have the framework to handle a back stack to provide back navigation.
There was one more first party method of creating UI elements that would could tap into, creating custom Android views. This route, we create a widget for each of our views. However, unlike Activities and Fragments, custom Views by default do not have back stack handlers, so you would have to implement back stack management on your own.
We’ve now exhausted the built in Android methods for creating user interfaces. Fortunately, we do not have to rely only upon the mechanisms that are built into Android to create our user interfaces, we can look to the community who have created tools to support the custom View method of creating user interfaces. One of those such tools is called Conductor, which we will use to start creating simple and great user interfaces.
What is Conductor?
On Conductor’s GitHub page, it is described as,
“A small, yet full-featured framework that allows building View-based Android applications.”
Let's look deeper and see how it fits for solving the problems of using Fragments, multiple Activities, or custom Views. You can have a single Activity run while switching out Conductor’s Controllers, just like using Fragments. Conductor’s Routers handle all of our back stack management for us, in a simpler manner than Fragments. Through either the built in animations and transitions, or creating your own, you can have simple, yet elegant transitions between your Views.
While Conductor does have a lifecycle to manage, it is far simpler than that of Fragments. You can view the Fragment lifecycle in the Android Fragment documentation and Conductor's Controller lifecycle can be found on the Conductor GitHub repository page.
Another aspect of Conductor being a small, yet full-featured framework is its simplicity. Most tasks only require a couple of lines of code and tend to work as one would expect them.
Ideally, if using Conductor, you would be putting your view logic into custom Views and using Conductor to do some minor management of those Views. This would keep everything modular in the case that you move to some other method of displaying custom Views. Another advantage of creating these modular views is that they can be reused in multiple scenarios no matter how your UI is being displayed.
Like Fragments, Conductor’s Controllers need a container to be able to be placed into.
The router handles our back stack management. The router is initialized via, while substituting in your context, container and saved instance state):
Then to transition from one controller to the other(while adding to the back stack) you issue the command
Conductor comes with some premade transitions, called ChangeHandlers, that can be used, or you can create your own custom ChangeHandlers.
For example, if you want to have the View slide in horizontally and slide out horizontally, you would do
router.pushController(RouterTransaction.with(Controller).pushChangeHandler(new HorizontalChangeHandler()).popChangeHandler(new HorizontalChangeHandler()));
From the previous examples, you might be wondering what is a Controller in the case of Conductor? Even though the term Controller is being used, Conductor isn’t just useful for Model-View-Controller design patterns. It can also be used for Model-View-Presenter, Model-View-ViewController, and any other design pattern you want to be using.
To create a basic Controller, you want to have a layout xml created, extend the Controller class and implement the onCreateView(LayoutInflater, ViewGroup) method. For example,
While Conductor may be a third party library, you can create responsive user interfaces that just work and don't have to put as much effort into managing multiple lifecycles. You get the advantages of using Fragments, without many of the drawbacks, which will allow you as the developer to focus on what really matters instead of figuring out what a Fragment is doing and why it won't react in the way you'd expect. As a developer it will be your choice if the benefits of using Conductor outweigh the reality of pulling in a third party library to handle something that has a first party component that does similar.
*Android is a trademark of Google LLC.
Graceful Android view composability has always been tough ball of yard to untangle, especially when apps grow and evolve across huge multi-team domains. Conductor is definitely interesting. It reminds of me Square's Flow (https://github.com/square/flow) and Mortar (https://github.com/square/mortar) libraries.
I'd also like to point out that I think Google has recognized the issues and has now begun introducing (long desired, much needed) guidance in this area via their Architecture Component (https://developer.android.com/topic/libraries/architecture/index.html) libraries. I expect more components released in the future to help us devs ease the pain of implementing quirky, long-standing Android'isms.
Thu, 12/21/2017 - 19:42