[Android] It’s time to use MVVM as your Android’s app base architecture — Part 1

Roy Ng @ Redso
AndroidPub
Published in
8 min readMay 31, 2017

MVVM-Model View ViewModel pattern

Data Binding (https://developer.android.com/topic/libraries/data-binding/index.html) was first introduced in 2015 and officially supported in Android Studio v2.0 at 2016. It allows you to bind your views and data together and support 2-way binding (using Observable pattern). It’s quite an essential tool for MVVM.

After 2 years, Google announces new collection of libraries that help us to design robust, testable, and maintainable apps using MVVM pattern (https://developer.android.com/topic/libraries/architecture/index.html). In this tutorial, I will try to convert a demo project from MVC to MVVM and tell you all my difficulties and concerns I encountered. Hoping in the end I can tell you It's time to use MVVM as your Android's app base architecture.

From MVC

It’s quite usually need to build an App that showing a list of content, the content usually have detail page, some forms the user need to fill. Therefore in this demo project I will start build these features using my pattern. The app requires:

  • Show a list of posts
  • Post detail page that showing the post’s content
  • User can write new post and the inputted fields need some validation

As an old school Android developer (develop Android since API 1.5 with short of tools and external libraries), I feel comfortable for god Activity (aka MVC pattern) for a long time. It’s allow me to control the business logic (flow of pages and flow of data) and interact with users using views in one place. By looking at the activity, I can scan what’s going on in that page (what kind of UIs are used and what kind of data are used). Of coz, I will delegate the responsibilities to different reusable components such as APIClient, ImageLoader… Activity class with 1000+ lines of codes will NOT happened on me but the activity is still the god.

From now, you can reference the codes in the demo project:
https://github.com/roytornado/Android-Demos/tree/master/MVVMDemo

The entry point of the app is PostListActivity. It’s used to show a list of post using RecyclerView. The posts are loading from server using ApiManager. Loading view is needed to represent the loading state of the list. PostsRecyclerViewAdapter help displaying the posts in the recycler view.

public class PostListActivity extends BaseActivity {

private RecyclerView mRecyclerView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_post_list);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setTitle(getTitle());
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(getBaseContext(), PostCreateActivity.class);
startActivity(intent);
}
});
mRecyclerView = (RecyclerView) findViewById(R.id.item_list);
loadPosts();
}

@Override
protected void onResume() {
super.onResume();
if (mRecyclerView.getAdapter() != null) {
mRecyclerView.getAdapter().notifyDataSetChanged();
}
}

private void loadPosts() {
showLoading();
ApiManager.sInstance.getPosts(new ApiManager.GetPostsCallback() {
@Override
public void onResponse(ArrayList<Post> posts) {
hideLoading();
displayPosts(posts);
}
});
}

private void displayPosts(ArrayList<Post> posts) {
mRecyclerView.setAdapter(new PostsRecyclerViewAdapter(posts));
}
}

It’s should be simple (short) enough for an Activity class. If I am a code reviewer for this app, I should know that this page (activity) display a list of posts using RecyclerView and able to create new post when the floating action button is clicked. If I need to change the layout, I go to the xml. If I need to change the UI’s flow, I change the logic in this class. If the data sources have problems, I go to ApiManager.

For PostCreateActivity:

public class PostCreateActivity extends BaseActivity {

private TextView postIdTextView;
private TextInputLayout postTitleTextInput;
private TextInputLayout postDescTextInput;
private AppCompatCheckBox agreeCheckBox;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_post_create);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setTitle(getTitle());
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}

postIdTextView = (TextView) findViewById(R.id.postIdTextView);
postTitleTextInput = (TextInputLayout) findViewById(R.id.postTitleTextInput);
postDescTextInput = (TextInputLayout) findViewById(R.id.postDescTextInput);
agreeCheckBox = (AppCompatCheckBox) findViewById(R.id.agreeCheckBox);

postTitleTextInput.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {

}

@Override
public void afterTextChanged(Editable s) {
if (s.length() > 0) {
postIdTextView.setText("" + s.length());
} else {
postIdTextView.setText("");
}
}
});
}

public void submit(View view) {
postTitleTextInput.setError("");
postDescTextInput.setError("");
if (postTitleTextInput.getEditText().getText().length() == 0) {
postTitleTextInput.setError("Please input title");
return;
}
if (postDescTextInput.getEditText().getText().length() == 0) {
postDescTextInput.setError("Please input title");
return;
}
if (!agreeCheckBox.isChecked()) {
showAlertMessage("Please agree T&C");
return;
}
showLoading();
// It's just mocked-up call. Nothing real here
ApiManager.sInstance.createPost(new ApiManager.CreatePostCallback() {
@Override
public void onResponse() {
hideLoading();
navigateUpTo(new Intent(getBaseContext(), PostListActivity.class));
}
});
}
}

Just a quick look. I know what kind of UIs (Textview, TextInputLayout, CheckBox…) is involved. What’s server side interaction (ApiManager.sInstance.createPost) is used.

This is more or less as same as pattern I used in other real project. The main advantage is that almost all important logic can be found in the activities.

It’s good for me actually. Therefore, when the Data Binding was first introduced, I resist to use it. The main reason that I don’t want to put the logic (what’s to display) in the XMLs. Use MVVM will be better? Let’s see.

To MVVM

I will convert the demo project to MVVM with the help of the new Android Architecture Components. You may need to take a look at it first.

What’s MVMM?

Sources: http://www.jianshu.com/p/4e3220a580f6
https://developer.android.com/topic/libraries/architecture/guide.html

You can also find the references at:

I will interpret the MVVM as following:

  • Model: The sources of the DATA in our app. Business Logic will be involved inside to process the data.
  • View: The UI displays to user. It’s know what DATA need to be displayed to users. It contain Pure UI Logic that control the change of the UIs.
  • ViewModel: Is responsible for exposing (converting) the DATA from the model in such a way that objects are easily managed and presented. It’s more modal than view (or I would say it’s a modal for view. abstracting from real modal). It contain UI Logic that involving both View and Model.

Data is the most important part that penetrate all three tiers. It’s normally a POJO object that contain properties only. Of coz, the data in Model may be converted (transformed) in ViewModel. Or some data may not even exist in Model.

Import:

allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
}
}
dependencies {
compile "android.arch.lifecycle:runtime:1.0.0-alpha1"
compile "android.arch.lifecycle:extensions:1.0.0-alpha1"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0-alpha1"
compile "android.arch.persistence.room:runtime:1.0.0-alpha1"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha1"
}

BaseActivity:

Because the App Architecture is not officially released yet, you may not able to use the default LifecycleActivity. In our demo project, the base activity is inherited from AppCompatActivity. Therefore, we need to make the base activity to support the Lifecycle. It’s easy:

public class BaseActivity extends AppCompatActivity implements LifecycleRegistryOwner {
LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
@Override
public LifecycleRegistry getLifecycle() {
return lifecycleRegistry;
}
}

It’s ready to convert our activities to MVVM now. At this moment, you need to think about this main question for each activity:

What should we put inside ViewModel?The answer is that put all things can be represented by pure POJO data objects into ViewModel.

We take PostListActivity as the first step.

MVVMPostListViewModel:

We create a class named “MVVMPostListViewModel” that extends ViewModel. Obviously, we can put “Post Data” inside the view modal that can be used by the View.

public class MVVMPostListViewModel extends ViewModel {

private MutableLiveData<List<Post>> postsData;

public LiveData<List<Post>> getPosts() {
if (postsData == null) {
postsData = new MutableLiveData<List<Post>>();
loadPosts();
}

return postsData;
}

private void loadPosts() {
ApiManager.sInstance.getPosts(new ApiManager.GetPostsCallback() {
@Override
public void onResponse(ArrayList<Post> posts) {
postsData.setValue(posts);
}
});
}
}

This view modal exposing the Post data objects (in terms of LiveData) to the View (MVVMPostListActivity). The posts are from the Modal (ApiManager).

MVVMPostListActivity:

The conversation is fairly simple.

  1. Get the view model in onCreate
  2. Observe the data you need and assign handler when the data ready
public class MVVMPostListActivity extends BaseActivity {

private RecyclerView mRecyclerView;
private MVVMPostListViewModel viewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_post_list);
...
viewModel = ViewModelProviders.of(this).get(MVVMPostListViewModel.class);
viewModel.getPosts().observe(this, this::displayPosts);
}

private void displayPosts(List<Post> posts) {
mRecyclerView.setAdapter(new PostsRecyclerViewAdapter(posts));
}
}

This activity is basically converted into MVVM. Comparing to the original codes. The loading state is not handled yet.

private void loadPosts() {
showLoading();
ApiManager.sInstance.getPosts(new ApiManager.GetPostsCallback() {
@Override
public void onResponse(ArrayList<Post> posts) {
hideLoading();
displayPosts(posts);
}
});
}

private void displayPosts(ArrayList<Post> posts) {
mRecyclerView.setAdapter(new PostsRecyclerViewAdapter(posts));
}

Originally, it will showLoading when the posts start loading and hideLoading when the call responded. For this kind of logic, it’s not Business Logic (not related to Model at all). It’s also not Pure UI Logic too. It’s UI logic that related to data: should change the UI when the status of data is changed. So it should be handled in ViewModel.

To solve this issue, we create a Data that represent that status of the ViewModel:

package com.redso.mvvmdemo.data;

public class ViewModelStatus {
public boolean isLoadingList;
}

Therefore, the MVVMPostListViewModel will know it’s status and provides the status as LiveData to the View.

public class MVVMPostListViewModel extends ViewModel {

private MutableLiveData<List<Post>> postsData;
private MutableLiveData<ViewModelStatus> status = new MutableLiveData<ViewModelStatus>();
private ViewModelStatus statusData = new ViewModelStatus();

public LiveData<ViewModelStatus> getStatus() {
return status;
}

private void loadPosts() {
statusData.isLoadingList = true;
status.setValue(statusData);
ApiManager.sInstance.getPosts(new ApiManager.GetPostsCallback() {
@Override
public void onResponse(ArrayList<Post> posts) {
postsData.setValue(posts);
statusData.isLoadingList = false;
status.setValue(statusData);
}
});
}
}

In MVVMPostListActivity:

viewModel.getStatus().observe(this, status -> {
if (status.isLoadingList) {
showLoading();
} else {
hideLoading();
}
});

This is the simplest way to handle resource loading status for a page. Google suggest another approach which can have it’s own status for each live data.
Addendum: exposing network status

Ok. Part 1 is finished. We have a demo project and converted to MVVM pattern.

Sources: https://github.com/roytornado/Android-Demos/tree/master/MVVMDemo

There will be Part 2 or 3 for the followings:

  • Convert PostDetailActivity and PostCreateActivity also
  • Make use of Room Persistence Library to store the post created
  • Should use Data Binding Library also?
  • Kotlin version

Please just give me a like (Rec) to encourage me.

Here is my summary so far:

  • View may be changed out of your control. MVVM help you separate ViewModel from View. App Architecture help you to get back the same ViewModel when View is re-created.
  • In MVVM, your UI is driven by your model.
  • The View (Activities, Fragments) will be very light and developers can focus on “displaying data” in View.
  • Data is the most important part in MVVM which penetrate all three tiers. The first thing you need to think when making a page is Data. Some data is not quite straightforward and obvious (e.g. Loading Status).
  • LiveData help you a lot in MVVM as it handle the life-cycle correctly for you. So you just need to focus on “displaying data” and “providing data”.

Ok, It's time to use MVVM as your Android's app base architecture.

--

--

Roy Ng @ Redso
AndroidPub

Redso: Expert in building iPhone apps, Android apps, Mobile Web, scalable backend on Cloud Platforms, and integrating them all together.