Clean Architecture with MVVMi, Architecture Components & RxJava

Luke Simpson
6 min readOct 24, 2017

--

Australia Post’s Digital iD is one of the most ambitious projects on which I’ve worked. We’ve replaced the physical process of providing 100 points of identity every time you apply for a loan or start a new job with a reusable solution that could revolutionise the digital economy here in Australia. It’s exciting to play a small role while working on the Digital iD Android app.

As one of three Android teams within Australia Post, we’re empowered to make improvements independent of the consumer and small business apps. A few months ago we started to discuss how we could speed up our delivery and release cycles while increasing test coverage, which led us to look into Android’s recently announced Architecture Components.

Getting Started with Architecture Components

Until this year, Google did not prescribe any architecture patterns for building Android apps. As a result, developers sometimes struggled to structure apps in a maintainable and testable fashion (see the Activity God object). Many also struggled to mange lifecycle issues, resulting in unfavourable behaviour and crashes. At Google I/O in May, the Android Guide to App Architecture was announced, along with several APIs to help us build Clean apps — these APIs are commonly known as Architecture Components.

Last month, Architecture Components moved to Beta release (except for the Paging Library) and was added to Support Library 26.1.0 and above. This means our activities can just extend AppCompatActivity instead of LifecycleActivity to take advantage of these components. Expect the full 1.0.0 release before the end of the year.

The two stars of the Architecture Components library are the ViewModel and LiveData. ViewModels outlive the Activity lifecycle and survive configuration changes, while LiveData is a lifecycle-aware observable data holder. This basically means that anything that happens while the app is in the background will be stored in the ViewModel’s LiveData, and the Ui will be updated when the app returns to the foreground.

If an API response is received while the app is in the background, LiveData knows not to update the UI until after onStart is called.

Our Implementation: MVVMi

We loved the power and simplicity of LiveData, so we dug a little deeper into how Google suggests we structure the ViewModel within our app.

Their prescribed architecture pattern is MVVM (Model-View-View Model) and it uses the ViewModel to source data from a repository, apply presentation logic and pass it to the view using LiveData. We immediately recognised a possible problem with this pattern: apps in the real world have several repositories for a single feature and the ViewModel could become bloated with the business logic required to map together these disparate data sources. We had seen the same problem in presenters when using the MVP (Model-View-Presenter) pattern and we wanted to avoid the same problem in our ViewModel.

If you’re familiar with presenters, note that ViewModels are not all that different. From an implementation perspective, the main difference is that a presenter uses an interface to glue itself to the view whereas the ViewModel uses LiveData.

One of our guiding principles during this process was to have a Clean, highly unit-testable architecture, with separation of concerns the highest priority. As such, we decided to add another layer between the ViewModel and repository that is responsible for all feature-specific business logic. This results in a lean ViewModel that is only responsible for presentation logic. We call this new layer the interactor and use MVVMi internally to distinguish our pattern from the standard MVVM.

Google’s MVVM architecture and our implementation with the added interactor layer (MVVMi).

To help us visualise all the moving parts we created a flow diagram showing the models we create in each layer and the types of tests required. We also included the responsibilities of RxJava and LiveData, which we’ll touch on later.

It’s easier to understand our justification for this extra layer with an example; imagine a screen with two states based on the results of two API calls:

Each API call is first mapped to a network model reflecting the structure of the JSON response, then to a somewhat generic domain model in the repository, which allows for re-use across the app. In the interactor we map those two calls into a feature-specific model that reflects the full set of data required for the feature. The ViewModel is now able to decide which pieces of data should be passed to the view by way of UiModels.

The power of this pattern lies in the way each layer can be replaced without affecting the other layers: Everything is modular and each layer only knows about the next layer up. For example, the view knows about the ViewModel, but not vice versa. To test each layer all we had to do was mock the next layer up. Except for the view, each layer could be tested using fast JUnit tests, requiring only a thin set of (slower) Espresso UI tests for the view. Our goal of quickly building, testing then releasing with confidence was getting closer.

What about RxJava?

RxJava is used heavily across Australia Post and we wanted to continue to take advantage of the convenience it offered. But we weren’t sure if LiveData observables negated the need for RxJava observables in any way. We quickly realised that LiveData would be used only on the View-ViewModel layers, and RxJava Observables would still be used to propagate data down to the ViewModel. In fact, RxJava is perfect for the chaining and mapping of API calls that occur in the interactor.

LiveData binds the ViewModel to the view, with RxJava used to chain and map API calls throughout the other layers.

More Examples!

Confused? We were too until we started building some features. We found the MVVMi structure actually helped guide our implementation of complex features by providing a clear roadmap for data processing and presentation logic. Here’s an example that shows the kind of logic you might put in each layer:

What’s Next?

Every day we are learning new things and tweaking our strategy, but we think we have a pretty solid design on which we can move forward. One outstanding issue for us is whether we need to use all of these layers for simple features, like a terms and conditions screen with static text. It certainly feels like overkill, but in a perfect world we would be consistent across all features.

We also hope to include end-to-end testing by simply configuring our UI tests to mock a server JSON response rather than the ViewModel layer. This would give us an incredibly high level of confidence that we’re avoiding regression issues.

If you are thinking about using any of these components remember that you don’t necessarily have to implement them exactly as prescribed in the architecture guide (or by us!) — some people are even using LifeCycle.EVENT to create a lifecycle-aware presenter. Just try to stick to Clean principles and always keep testing at the forefront of any architectural decisions you make.

For some useful code snippets, check out Darren Kong’s MVVMi example app here.

--

--