Android Architecture Components MVVM — Part 1

Simon Joecks
ProAndroidDev
Published in
5 min readAug 25, 2017
from DevExpress

We recently started a new project in HERE Technologies that allowed us to check-out new technologies like Architecture Components and popular trends like Kotlin, an emerged first class citizen of Android. While working on the new project a familiar MVVM architecture emerged, likely inspired by Google blueprints todo app (mvvm-live), but with a stronger focus on readability/ maintainability and testability of the binding between view and view-model. We will not discuss a new pattern but more likely show how LiveData and ViewModel can be used to create a scalable application for real life requirements.

Checkout and contribute to the framework on Github.

The Problem

All aspects of this article are implemented in an interpretation of Googles mvvm-live blueprint application and are available on github (dev-todo-mvvm-live-single), so go ahead and checkout the code to see how this works in real life.

An abstract diagram of the MVVM pattern. In Android Models are usually everything related to network or system resources. Check out the clean architecture for further reading.

The mvvm-live architecture by the google example relies on a direct connection between the view-model and the individual views using data-binding. Each individual view is bidirectional bound to an observable property or LiveData within the view-model. The view-model then communicates with the model that is usually represented by use-cases and repositories.

The benefits of this architecture are that any element on the screen can regain its previous view state when rebinding to a retained view-model. And little code is necessary to achieve a bidirectional communication with individual properties.

A major issue with this framework is that the view-model clutters with a multitude of view-dependent aspects even though it should not be exposed to the actual view implementation (but only to its needed data objects). Thus, it becomes way more difficult to abstract and reuse view-models for similar view patterns or to test a consistent view state, because it can be altered by multiple sources. The below diagram exemplifies this:
Each individual view (TextView, ImageView) observe a field (mTitle, mIcon) within the view-model and it can potentially alter it (like with an EditText).

The mvvm-live architecture: Individual view-aspects are bound to views, and so the view-model becomes very closely designed to the actual view.

The following will show some improvements on this architecture by using immutable view data that is used to represent the view state. We will also discuss how to approach navigation and view hierarchy to utilize the benefits of ViewModel and LiveData in its best.

The Rules

After an initial implementation using live-data and observables for almost any aspect of application (Do not use live data to completely control your application flow, this is a good way to shoot yourself in the foot) we stepped back reviewed the issues and derived rules for a more scalable application:

  1. A view observes only one observable (i.e. LiveData) within the view-model.
  2. A view-model can observe multiple sources (i.e. LiveData , LocationProvider, use-cases, etc.)
  3. All view interactions are calls to the view-model.

Especially the first rule has some serious implications for the view/view-model relationship. The view does not observe multiple aspects of the view model but just a single source of truth that is the immutable representation of the view state, which we call ViewData. That also means that you design your view just right to fit within the view-model, and likely also to reuse it, once you have sliced out individual and independent aspects of your view (Read more in Part 2).

The second rule just describes the familiar principle within mvvm and clean architecture, that it is only allowed to query / control external interfaces within the view-model.

The third rule helps with user interaction and other external events like pressing the home button, receiving a push notification and on how to design navigation and inter model communication.

The view-model holds a single representation of the view state, an immutable ViewData object that then will be observed and bound to the view.

Navigation and inter-model communication

The 3rd rule helps structure navigation and inter-model communication that is necessary to make decisions on higher and more abstract levels. Let’s look at a simple case where one Activity is showing different Fragments for aspects like task details, task list or editing a task. In such a case, the MainActivity is rendering data objects like TasksData or TaskDetails[id]to Fragments. So, the MainViewModelis the only source of truth which kind of state will be displayed on the screen.

()A simple pattern that can be repeated within an application hierarchy. Here the MainActivity decides which fragment will be shown.

Then the forward communication is simple, the shown TasksFragment is created with parameters from the MainViewModel.Data like the id of the task — or other aspects that are necessary to recreate the data context of the fragment — and then the TasksViewModel will be initialized with those parameters.

The MainViewModel Data will be rendered on the MainActivity that is basically showing/ updating fragments. Then the fragment is repeating the pattern and rendering its data to the AndroidView.

But how does the TasksFragment then interact with the MainViewModel, how does it callback on events like when the users selected a task that should be edited?
The TasksViewModel defines an interface (TasksActions) that provides callbacks on high level user actions (i.e. onTaskDetail, onTaskEdit etc.) and the MainViewModel implements these actions, so that it can be injected in the TasksViewModel.

Each ViewModel declares callback interfaces that other ViewModels can callback, like TasksViewModel who declares callbacks for onTaskDetail(id) and the MainViewModel implements those callbacks.
The TasksViewModel with all its incoming and outgoing dependencies.

Note that the TasksViewModel has no knowledge of the MainViewModel but only that it requires an implementation of its defined interface. This can be achieved by dependency injection (we had some good experience with Kodein).

This pattern can also be used to support inter-fragment communication, when two or more fragments (Fragment, Fragment 2) are visible at the same time, as shown in the complete diagram example below:

Example diagram of a hierarchical communication where the ApplicationModel decides on which Activities should be shown, ActivityModels which Fragments to be rendered, and Fragments which individual views should be displayed.

Note: Fragments can be used to isolate large reusable views defined by the 3-touple: View, ViewData, ViewModel. But Fragments are not necessary and a similar 3-touple can be created with custom views (living also within a Fragment or directly within an activity).

Conclusion

  • No need for a new MVVM framework just 3 simple rules
  • Design your ViewModel so that they clearly interface with the view by using a view interface (ViewData)
  • Observables can be useful but should be used with a clear intention and control flow

Part 2: ViewData, Backstack Behavior, Retaining ViewData

Recommended Reading

  1. Source code for the sample app in this article is available at https://github.com/joecks/android-architecture/tree/dev-todo-mvvm-single-live
  2. Model-View-ViewModel App Architecture from Florina Muntenescu
  3. The Clean Architecture
  4. James Shvarts Article on MVVM, LiveData, RxJava, Dagger

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Simon Joecks

Flutter / Android Developer/ Engineer at Xayn / Freelancer

Responses (2)

Write a response

Recommended from Medium

Lists

See more recommendations