All Ruby on Rails Node JS Android iOS React Native Frontend

VVM Concept Using Architecture Components

Introduction

The most important question when we create a new Android application from scratch is which architecture should we choose. Developers can choose between MVC, MVP, MVI and MVVM (among others). These architectural patterns are popular for Android development. For years, developers had to implement from scratch, by themselves, the application skeleton without any SDK support. After many years, at the annual Google I/O event, in May 2017, Google presented the Architecture Guide, in which object lifecycle is the most important functionality to support from the point of view of Android OS. During that I/O, Google presented some examples of Android Architecture Blueprints.

Google Approach

A blueprint is the skeleton of an application, in which the basic implementation of the architectural pattern is included. Google provides native libraries to support the development of MV* architectures, called Architecture Components. The character * means all kinds of architectural patterns based on the Model (data source) and View (the UI presentation of the data source). Here you can find some examples of the patterns made by Google: https://github.com/googlesamples/android-architecture. One of the examples that Google provides is the architectural blueprint called Todo-mvvm-live. This blueprint uses Android Architecture Components such as ViewModel, LiveData, and other lifecycle-aware classes. The above-mentioned architectural blueprint is based on the todo-mvvm-databinding sample, which uses the Data Binding Library to display data and bind UI elements to actions. In this blueprint, Google presents AndroidViewModel, which is context aware. Moreover, Google presents a new SingleLiveEvent class, which extends MutableLiveData, so it’s also lifecycle-aware. This is used for communication between ViewModels and UI views (activities and fragments). The prefix Live in SingleLiveEvent suggests that we should expect something that is adjacent to Lifecycle, but, in reality, SingleLiveEvent implementation allows to emit data to only one observer and only once – which makes the prefix Live a bit artificial. The concept of LiveData is strictly related to Lifecycle, and the main idea was to emit data every time the Lifecycle state changes.

The Blueprint

Based on the concept of Google Architectural Blueprints and my own experience as an Android developer, I have created an architectural blueprint. My proposal is based on the MVVM design pattern with Data Binding Library, Android Architecture Components, and RxRelay. From the developer’s point of view, the most important thing is to separate the code related to the Android SDK. The reason for this separation is the need to test business logic in unit tests and avoid testing the Android Context. The UI Espresso tests should cover most of the Context-depent classes such as Activities, Fragments, Services, ContentProviders, etc. Another important thing is to develop an application as part of the architecture and create the minimum required implementation. In my blueprint, I have the requirements that online data are delivered, and there is no need to have a local database or cache for data persistence. To fulfil both requirements, I made two significant changes comparing to Google’s blueprint. Firstly, the Model is removed from the architecture, and the layer responsible for data access is moved to the ViewModel. Secondly, the business logic related to UI from AndroidViewModel moved to the ViewModel is not context aware, and it is testable.

The architectural assumptions in my blueprint

  • Features in the application are represented by Activities.
  • Features have scenarios represented by one or more screen Fragments.
  • The Application Session Data (ASD) for features are shared between all scenario Fragments belonging to the Activity in a single ViewModel created in the Activity. Application Session Data is the data stored in the ViewModel and their Lifecycle is identical to ViewModel’s lifecycle.
  • The communication from UI to the ViewModel is delivered through Data Binding.
  • The communication from the ViewModel to the Activity or Fragments is delivered by a communication bus called ActionEvent.
  • The ViewModel contains the LiveData objects that keeps the Data Transfer Objects (DTO) that were delivered from asynchronous calls. Those objects are observed in the Activity or Fragment. A Data Transfer Object is an object that is used for encapsulating data and sending it from one subsystem of an application to another. DTOs are most commonly used by the Services layer to transfer data between itself and the UI layer.
  • Each Activity has his own Subcomponent from the Dagger 2 dependency tree.
  • ContributesAndroidInjector annotation reduces boilerplate code.
  • Each Fragment using ContributesAndroidInjector annotation is a child of the related Activity on the Dagger Module list.
  • Each Subcomponent defines a graph of injected ViewModels related to the Activity.
  • ViewModels are unit-testable.

The diagram below shows the structure of the described concept.

Zrzut ekranu 2018-09-26 o 15.03.26

The diagram shows the two main domains of responsibility in the cross-section of the application’s architecture. One is dependent on the Android Context. That domain contains the Activities, Fragments, Services, and ContentProviders. The other domain is Application Session Data, which contains the ViewModel and also the DTO product of the asynchronous calls. There are three main flows that communicate between Application Session Data and Android Context. The flow, LiveData, is observed by the Android Context. The second flow is Data Binding itself. The third, innovative, flow in my blueprint is that instead of Google’s SingleLiveEvent, I proposed something much more general: ActionEvent.


  class ActionEvent : ViewModelEvent {
   var startProgressRelay: PublishRelay = PublishRelay.create()
   var successRelay: PublishRelay = PublishRelay.create()
   var errorRelay: PublishRelay = PublishRelay.create()
   override fun onSuccess(t: T) {
       successRelay.accept(t)
   }
   override fun onError(t: Throwable) {
       errorRelay.accept(t)
   }
   override fun onStartProgress() {
       startProgressRelay.accept(true)
   }
   fun getStartProgressEvent(): Flowable {
       return startProgressRelay.toFlowable(BackpressureStrategy.LATEST)
   }
   fun getSuccessStream(): Flowable {
       return successRelay.toFlowable(BackpressureStrategy.LATEST)
   }
   fun getErrorStream(): Flowable {
       return errorRelay.toFlowable(BackpressureStrategy.LATEST)
   }
}

interface ViewModelEvent {
   fun onStartProgress()
   fun onSuccess(t: T)
   fun onError(t: Throwable)
}

An Action Event is a general concept to handle events in the ViewModels. Those events can come from:

  • Data Binding actions (e.g. onClick, TextChangedListener), or
  • Asynchronous calls (e.g. AsyncTask, API service call, WorkRequest).

The reason why the Action Event uses separate streams to notify of a Success or an Error is the ability to handle errors many times from the same source. This can happen when an API service call ends many times with a failure and a Throwable needs to be passed to the error handling system. In ActionEvent, the error stream errorRelay: PublishRelay<Throwable> is used to put a Throwable as an onNext value. The BackpressureStrategy LATEST keeps only the latest onNext value, overwriting any previous value if the downstream can't keep up. The ActionEvent populates on the error stream not only one Throwable but many Throwables, and that is the latest Throwable value. This solution is just more reliable and flexible compared to the SingleLiveEvent taken from Google’s blueprint. This implementation is more suited to the behavior we want to achieve. SingleLiveEvent artificially blocks listening from observers in LiveData. The implementation and the name of the SingleLiveEvent do not exactly fit the reality, because it is not a “real” live object. ActionEvent is also extendable to other independent streams that will support more advanced and complex event handling (i.e startProgressRelay is for animation handling). Another innovative change in my blueprint is to keep only the DTO (Data Transfer Objects) as LiveData. Such object can stay composite in the ViewModel and be observed in the Activity or Fragment context. This ensures that each time a Lifecycle changes, we will have the latest DTO. Other types of data that are not DTO are simply stored in the ViewModel, not as LiveData.

Summary

Developers need to pay attention to the logic that can be placed in the Context. It is important to move the business logic away as far as possible from the Context.

This architecture requires that we add the Model when a data source needs to have a local database or local cache in the application. It can happen that a ViewModel needs to be shared between two and more Activities but not Fragments. This unusual case happened to me when an application that I was developing had a more iOS-like design. In that case, a shared ViewModel can be injected within the application scope as a @Singleton or @ApplicationScope. My blueprint is a solution for developers who are familiar with the MVVM design pattern and know such Architecture Components as LiveData, Data Binding Library, and ViewModel. This blueprint is also for developers who are designing applications where there is no disk cache or database within the application.

Let's make the world better for everyone - join us
New Call-to-action
READ ALSO FROM Android
Read also
Need a successful project?
Estimate project or contact us