Android Architecture Patterns Part 3:
Model-View-ViewModel

Florina Muntenescu
Nov 4, 2016 · 6 min read

After four different designs in the first six months of the development of the upday app, we learned one important lesson: we need an architecture pattern that allows fast reaction to design changes! The solution we chose in the end was Model-View-ViewModel. Discover with me what MVVM is; how we are applying it at upday and what makes it so perfect for us.

The Model-View-ViewModel Pattern

  • 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 class structure

Model-View-ViewModel at upday

DataModel

Our strong emphasis on the single responsibility principle leads to creating a DataModel for every feature in the app. For example, we have an ArticleDataModel that composes its output from the API service and database layer. This DataModel handles the business logic ensuring that the latest news from the database is retrieved, by applying an age filter.

ViewModel

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 email address of a User, rather than creating two streams for this, we create a DisplayableUser object that encapsulates the two fields. The stream will emit every time the display name or the email changes. This way, we ensure that our View always displays the current state of the User.
  • 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.

We wrote about these two topics in a blog post about common mistakes in MVVM + RxJava.

View

private final CompositeSubscription mSubscription = new CompositeSubscription();

@Override
public void onResume() {
super.onResume();
mSubscription.add(mViewModel.getSomeData()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateView,
this::handleError));
}

@Override
public void onPause() {
mSubscription.clear();
super.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.

private final CompositeSubscription mSubscription = new CompositeSubscription();public MyView(Context context, MyViewModel viewModel) {
...
mSubscription.add(mViewModel.getSomeData()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateView,
this::handleError));
}
@Override
public void onDetachedFromWindow() {
mSubscription.clear();
super.onDetachedFromWindow();
}
}

Testability Of The Model-View-ViewModel Classes

DataModel

ViewModel

Consider the following example where the ViewModel just exposes some data from the DataModel:

public class ViewModel {
private final IDataModel mDataModel;
public ViewModel(IDataModel dataModel) {
mDataModel = dataModel;
}
public Observable<Data> getSomeData() {
return mDataModel.getSomeData();
}
}

The tests for the ViewModel are easy to implement. With the help of Mockito, we are mocking the DataModel and we control the returned data for the methods used. Then, we make sure that when we subscribe to the Observable returned by getSomeData(), the expected data is emitted.

public class ViewModelTest {@Mock
private IDataModel mDataModel;
private ViewModel mViewModel;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mViewModel = new ViewModel(mDataModel);
}
@Test
public void testGetSomeData_emitsCorrectData() {
SomeData data = new SomeData();

Mockito.when(mDataModel.getSomeData())
.thenReturn(Observable.just(data));
TestSubscriber<SomeData> testSubscriber =
new TestSubscriber<>();
mViewModel.getSomeData().subscribe(testSubscriber); testSubscriber.assertValue(data);
}
}

If the ViewModel needs access to Android classes, we create wrappers that we call Providers. For example, for Android resources we created a IResourceProvider, that exposes methods like String getString(@StringRes final int id). The implementation of the IResourceProvider will contain a reference to the Context but, the ViewModel will only refer to an IResourceProvider injected.

As we have mentioned above, and in our common mistakes blog post, 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

Is MVVM The Right Solution?

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.

Conclusion

After the design changes during the “infancy” of our app, we switched to MVVM in upday’s “adolescence” — a period of mistakes from which we learned a lot. 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 upday a mature app.

A simple example of the MVVM implementation can be found here.A “Hello, World!” comparison between MVP and MVVM can be found here. Check out also our post on Model-View-Presenter.

upday devs

upday tech blog

Florina Muntenescu

Written by

Android Developer Advocate @Google

upday devs

upday tech blog