Android ViewModels means Composition

With Google’s new ViewModel library comes a new slew of tutorials, documentation, blog posts, and github repositories describing it’s uses and how to get it up and running. One gigantic thing I think a lot of people miss in these tutorials is one of the core concepts of MVVM, composition.
The full code for this post in a fully runnable sample app can be found here.
Composition allows us to create something that is greater than the sum of it’s parts. For example, let’s imagine for a moment that I have the following screen:

Notice that we can break this screen down into several different parts:
- The Header (Toolbar)
- The Content (Middle Section)
- The Footer (Bottom bar)
Each of these components perform different tasks, and each one can have it’s own ViewModel object where these tasks are performed. Doing this allows for a few niceties:
- Great Separation of Concerns. Each ViewModel is small, focused, and easy to digest.
- Easy testability, again due to the small, isolated nature of ViewModels.
- Reusability / modularization. By segregating presentation logic for different components, we now have reusable components. For example, it is trivial now to use the same toolbar on multiple pages.
- Minimization of Activity Logic. Our activity becomes a dependency injector / router for our application, and nothing more. This makes testing it almost completely unnecessary, besides some minor UI tests to verify we go where we expect when we interact with the bottom bar.
So how do we wire all of these together? Utilizing Android Databindings, we end up with the following binding pairs:
- main_activity.xml / MainActivityBinding
- page1.xml / Page1Binding
- toolbar.xml / ToolbarBinding
- bottom_bar.xml / BottomBarBinding
We end up with the following ViewModels:
- Page1ViewModel
- ToolbarViewModel
- BottomBarViewModel
Note that Page 2 does not have a ViewModel or Binding. This is because it is just a simple text view, and does not require one.
Our MainActivity XML file looks like such:
Note our use of the include
tag, along with an ID on each of these tags. This will allow us to inject child bindings into our generated MainActivityBinding
.
Our Main Activity is as noted earlier. It provides dependencies to it’s ViewModels and some routing code. In this sample application, we avoid Dagger altogether, and instead lean on the ViewModels acting like singletons with respect to a given Activity, since this is all we really need.
Note that the addition of IDs to the include
tags has automatically set up multiple view bindings within our layout, and we simply need to set our ViewModels up.
The ToolbarService here is a ViewModel which simply allows different ViewModels to inform the Toolbar as to what it should display. The other route here is of course to allow ViewModels to talk directly to each other, but I deviated from this and followed closer what is done in frameworks like AngularJS, purely for educational purposes. This can be viewed as an over-abstraction, and is perfectly fine to leave out.
This singleton pattern for ToolbarService
could also be applied via a normal Dagger injection. One way is to make the factory
itself injectable, and then simply provide the right ViewModel
via a Provider
as they are requested. This would allow Dagger and the ViewModelProviders
to work happily together.
Due to the nature of ViewModels, we can safely open the app on a device and see that rotating the display will leave you exactly where you expect, and all state is maintained and respected.
For more information on some of the nitty gritty details around factories and the like, I’d refer you happily to the article here, by a colleague of mine: https://proandroiddev.com/architecture-components-modelview-livedata-33d20bdcc4e9
Utilizing multiple models per screen helps build modular, finely grained, testable, maintainable applications for you and your users.
Hopefully through this process I’ve sparked some intuition about how ViewModels can be viewed and treated. Utilizing multiple models per screen helps build modular, finely grained, testable, maintainable applications for you and your users. It can help relieve the headache of large source files, and can help to maintain a cleaner code base throughout a long, large project.