Android Architecture Patterns : Model-View-ViewModel
Since joining Twiga Foods, I have been involved majorly in the development of our Android App. During the design and development of the first version of the app, there was really no Architecture Pattern that was followed. Many changes were made after deployment, and they learned one important lesson: They needed an architecture pattern that allows fast reaction to design changes! The solution we chose in the end was Model-View-ViewModel after some discussions. Discover with me what MVVM is; how we are applying it at Twiga Foods for our second version and what makes it so perfect for us.
The Model-View-ViewModel Pattern
The main players in the MVVM pattern are:
- The View — that informs the ViewModel about the user’s actions
- The ViewModel — exposes streams of data relevant to the View
- The DataModel — abstracts the data source. The ViewModel works with the DataModel to get and save the data.
At a first glance, MVVM seems very similar to the Model-View-Presenter pattern, because both of them do a great job in abstracting the view’s state and behavior. The Presentation Model abstracts a View independent from a specific user-interface platform, whereas the MVVM pattern was created to simplify the Event Driven programming of user interfaces.
If the MVP pattern meant that the Presenter was telling the View directly what to display, in MVVM, ViewModel exposes streams of events to which the Views can bind to. Like this, the ViewModel does not need to hold a reference to the View anymore, like the Presenter is. This also means that all the interfaces that the MVP pattern requires, are now dropped.
The Views also notify the ViewModel about different actions. Thus, the MVVM pattern supports two-way data binding between the View and ViewModel and there is a many-to-one relationship between View and ViewModel. View has a reference to ViewModel but ViewModel has no information about the View. The consumer of the data should know about the producer, but the producer — the ViewModel — doesn’t know, and doesn’t care, who consumes the data.
Model-View-ViewModel using LiveData and Room
The event driven part required by MVVM is done using LiveData, which is part of the Android Architecture Components. LiveData is a data holder class that can be observed within a given lifecycle. This means that an Observer can be added in a pair with a LifecycleOwner , and this observer will be notified about modifications of the wrapped data only if the paired LifecycleOwner is in active state. Here’s how you can apply MVVM in your Android App:
DataModel
The DataModel exposes data easily consumable through event streams — LiveData. It composes data from multiple sources, like the Network Layer, Room Database or Shared Preferences and exposes easily consumable data to whomever needs it. The DataModels hold the entire business logic.
Our strong emphasis on the single responsibility principle leads to creating a DataModel for every feature in the app. For example, we have a FarmerModel that composes its output from the API service and Database Layer. This DataModel handles the business logic ensuring that the latest farmers from the database are retrieved.
ViewModel
The ViewModel is a model for the View of the app: an abstraction of the View. The ViewModel retrieves the necessary data from the DataModel, applies the UI logic and then exposes relevant data for the View to consume. Similar to the DataModel, the ViewModel exposes the data via Observable
s.
We learned two things about the ViewModel the hard way:
- The ViewModel should expose states for the View, rather than just events. For example, if we need to display the Name and the Phone Number of a
Farmer
, rather than creating two streams for this, we create aDisplayableFarmer
object that encapsulates the two fields. The stream will emit every time the display name or the phone number changes. This way, we ensure that our View always displays the current state of theFarmer
. - We should make sure that every action of the user goes through the ViewModel and that any possible logic of the View is moved in the ViewModel.
View
The View is the actual User Interface in the app. It can be an Activity
, a Fragment
or any custom Android View
. For Activities
and Fragment
s, we are binding and unbinding from the event sources on onResume()
and onPause()
.
If the MVVM View is a Custom Android View
, the binding is done in the constructor. To ensure that the subscription is not preserved, leading to possible memory leaks, the unbinding happens in onDetachedFromWindow
.
Testability Of The MVVM Classes
One of the main reasons why we love the Model-View-ViewModel pattern is that it is so easy to test.
DataModel
The use of inversion of control pattern, heavily applied in our code, and the lack of any Android classes, facilitate the implementation of Unit Tests of the DataModel.
ViewModel
We see the Views and the Unit Tests as two different types of consumers of data from the ViewModel. The ViewModel is completely separated from the UI or any Android classes, therefore straightforward to Unit Test.
The tests for the ViewModel are easy to implement. With the help of Mockito, we can be able to mock the DataModel and we control the returned data for the methods used. Then, we can make sure that when we subscribe to the Observable
returned by getSomeData()
, the expected data is emitted.
We are creating Model Objects to hold the state of the data. This also allows a higher degree of testability and control of the data that is emitted by the ViewModel.
View
Given that the logic in the UI is minimal, the Views are easy to test with Espresso. You can also using libraries like DaggerMock and MockWebServer to improve the stability of our UI tests.
Is MVVM The Right Solution for us at Twiga Foods?
We fully decided and agreed to use MVVM Architecture Patter for our Android App.
We have seen that since the View is just a consumer of the ViewModel, it was easy to just replace different UI elements, with minimal, or sometimes zero changes in other classes.
We have also learned how important separation of concerns is and that we should split the code more, creating small Views and small ViewModels that only have specific responsibilities. The ViewModels are injected in the Views. This means that most of the times, we just add the Views in the XML UI, without doing any other changes. Therefore, when our UI requirements change again, we can easily replace some Views with new ones.
Super Cool!!!
Conclusion
MVVM combines the advantages of separation of concerns provided by MVP, while leveraging the advantages of data bindings. The result is a pattern where the model drives as many of the operations as possible, minimizing the logic in the view.
Now, we can be proud of an app that has proven its resistance to another redesign. We are finally close to being able to call our Twiga App [DMS] a mature app.
I would love to hear your views and opinions on the different Architecture Patterns you have used before, and why you chose them. Remember to share.