MVVM Architecture with LiveData Android

Introduction

 
Why MVVM, when there are various other architectures available, such as MVC or MVP? MVP has been a very popular architecture among Android developers but it has some disadvantages. First, we must know its architecture. Architecture is "Building an application with a certain set of rules along with the proper implementation of protocols and functionalities."  Good architecture helps with the maintainability and testability of code. Consider a situation where you don't have any architecture and there are lots of activities, fragments, adapters, model classes and so on in a single unit. If you want to change something in a fragment, then searching and changing a line of code is like searching through a favourite t-shirt in a large pile of clothes. It seems messy and takes a lot of time to do that. So, why MVVM?
 

Why MVVM?

 
Why not MVP or something else. Well, let's talk about a very popular architecture, Model View Presenter (MVP). 
  • Model: Model has the manipulation of data and is business logic. The model does not directly contact with a view, instead, its communicated by the presenter.
  • View: View is mainly the UI part. It also communicates with the presenter. View has the presenter as a reference.
  • Presenter: It presents the data to the view which is obtained from the model. The Presenter has the reference of the View. Actually, View and Presenter here are tightly coupled. 
MVVM Architecture with LiveData Android
Here we can see that Presenter is responsible for updating View with the data. There is a disadvantage to this (MVP) approach:
  • There is a tight coupling between Presenter and View. If an Activity is destroyed and the presenter keeps trying to update the view, the application crashes because view was not there for some reason. It's the same case with the configuration change.
  • Interfaces are very huge in number, acting between these layers.

MVVM

 
MVVM stands for Model View ViewModel. It has overcome this issue in the MVP approach by making the components loosely coupled. It works on the concept of observables. Children don't have reference to the parent, they only have reference by observables.
 
Model
 
It has business logic with a repository, which in turn has local and remote data classes. The repository communicates with local or remote data sources according to the request from ViewModel.
 
View
 
View consists of a UI part (i.e., Activity, Fragment and so on). Clickable buttons or any action is sent to the ViewModel, but does not directly get a response. To get a response, view observes some data which ViewModel exposes. This means that it works on the Observer and Observable pattern.
 
ViewModel
 
ViewModel has all UI logic part there. ViewModel works as a bridge between View and Model class. ViewModel does not have a direct reference to the View class, instead, it sends data through observables. The View part has observed data and UI gets updated when data changes observable notifies.
 
Let's see the image for better understanding:

MVVM Architecture with LiveData Android
 
How does this differ from MVP?
  • ViewModel replaces the Presenter in the Middle Layer.
  • The Presenter holds references to the View. The ViewModel doesn’t.
  • The Presenter updates the View using the classical way (triggering methods).
  • The ViewModel sends data streams.
  • The ViewModel does not have a clue about the View or Views is listening to it.
Here is how we are going to implement this info:
 

MVVM using LiveData

 
This is an Observable data holder class. Let's see some brief information about LiveData.
 
LiveData
 
According to the docs "LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.".
 
Advantages of LiveData
  • No memory leaks: Observers are bound to Lifecycle objects and clean up after themselves when their associated lifecycle is destroyed.
  • No crashes due to stopped activities: If the view is destroyed or is in backstack, it will not update the view. Crashing can be avoided using this approach.
  • No more manual lifecycle handling: Since LiveData automatically manages the lifecycle changes in UI since it is lifecycle aware.
  • Proper configuration change: If configuration change happens when the device gets rotated, then it receives the latest available data. 
Let's create an application that demonstrates MVVM with LiveData. Create a list using recyclerview. Add some dependencies to build.gradle (app level).
  1. apply plugin: 'com.android.application'  
  2.   
  3. android {  
  4.     compileSdkVersion 28  
  5.     defaultConfig {  
  6.         applicationId "com.example.com"  
  7.         minSdkVersion 21  
  8.         targetSdkVersion 28  
  9.         versionCode 1  
  10.         versionName "1.0"  
  11.         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"  
  12.     }  
  13.     buildTypes {  
  14.         release {  
  15.             minifyEnabled false  
  16.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
  17.         }  
  18.     }  
  19. }  
  20.   
  21. dependencies {  
  22.     implementation fileTree(dir: 'libs', include: ['*.jar'])  
  23.     implementation 'com.android.support:appcompat-v7:28.0.0'  
  24.     implementation 'com.android.support.constraint:constraint-layout:1.1.3'  
  25.     testImplementation 'junit:junit:4.12'  
  26.     androidTestImplementation 'com.android.support.test:runner:1.0.2'  
  27.     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'  
  28.   
  29.     // Lifecycle components  
  30.     def archLifecycleVersion = '1.1.1'  
  31.     implementation "android.arch.lifecycle:extensions:$archLifecycleVersion"  
  32.     annotationProcessor "android.arch.lifecycle:compiler:$archLifecycleVersion"  
  33.   
  34.     // Glide  
  35.     def glideVersion = '4.8.0'  
  36.     implementation "com.github.bumptech.glide:glide:$glideVersion"  
  37.     annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"  
  38.   
  39.     //RecyclerView  
  40.     implementation 'com.android.support:recyclerview-v7:28.0.0'  
  41.   
  42.     //Support Design for Coordinator layout  
  43.     implementation 'com.android.support:design:28.0.0'  
  44.   
  45.     //circle imageview  
  46.     implementation 'de.hdodenhof:circleimageview:2.2.0'  
  47. }  
Now add Internet permission to your manifest. See AndroidManifest.xml
  1. <uses-permission android:name="android.permission.INTERNET"/>  
Lets create a data model class. Name it NicePlace.java
  1. public class NicePlace {  
  2.   
  3.     private String title;  
  4.     private String imageUrl;  
  5.   
  6.     public NicePlace(String imageUrl, String title) {  
  7.         this.title = title;  
  8.         this.imageUrl = imageUrl;  
  9.     }  
  10.   
  11.     public NicePlace() {  
  12.     }  
  13.   
  14.     public String getTitle() {  
  15.         return title;  
  16.     }  
  17.   
  18.     public void setTitle(String title) {  
  19.         this.title = title;  
  20.     }  
  21.   
  22.     public String getImageUrl() {  
  23.         return imageUrl;  
  24.     }  
  25.   
  26.     public void setImageUrl(String imageUrl) {  
  27.         this.imageUrl = imageUrl;  
  28.     }  
  29.   
  30. }  
Let's create a data set from the Repository class.
  1. /** 
  2.  * Singleton pattern 
  3.  */  
  4. public class NicePlaceRepository {  
  5.   
  6.     private static NicePlaceRepository instance;  
  7.     private ArrayList<NicePlace> dataSet = new ArrayList<>();  
  8.   
  9.     public static NicePlaceRepository getInstance(){  
  10.         if(instance == null){  
  11.             instance = new NicePlaceRepository();  
  12.         }  
  13.         return instance;  
  14.     }  
  15.   
  16.   
  17.     // Pretend to get data from a webservice or online source  
  18.     public MutableLiveData<List<NicePlace>> getNicePlaces(){  
  19.         setNicePlaces();  
  20.         MutableLiveData<List<NicePlace>> data = new MutableLiveData<>();  
  21.         data.setValue(dataSet);  
  22.         return data;  
  23.     }  
  24.   
  25.     private void setNicePlaces(){  
  26.         dataSet.add(  
  27.                 new NicePlace("https://c1.staticflickr.com/5/4636/25316407448_de5fbf183d_o.jpg",  
  28.                         "Falls")  
  29.         );  
  30.         dataSet.add(  
  31.                 new NicePlace("https://i.redd.it/tpsnoz5bzo501.jpg",  
  32.                         "Trondheim")  
  33.         );  
  34.         dataSet.add(  
  35.                 new NicePlace("https://i.redd.it/qn7f9oqu7o501.jpg",  
  36.                         "Spain")  
  37.         );  
  38.         dataSet.add(  
  39.                 new NicePlace("https://i.redd.it/j6myfqglup501.jpg",  
  40.                         "Rocky Mountain Park")  
  41.         );  
  42.         dataSet.add(  
  43.                 new NicePlace("https://i.redd.it/0h2gm1ix6p501.jpg",  
  44.                         "Havasu Falls")  
  45.         );  
  46.         dataSet.add(  
  47.                 new NicePlace("https://i.redd.it/k98uzl68eh501.jpg",  
  48.                         "Mahahual")  
  49.         );  
  50.         dataSet.add(  
  51.                 new NicePlace("https://c1.staticflickr.com/5/4636/25316407448_de5fbf183d_o.jpg",  
  52.                         "Lake")  
  53.         );  
  54.         dataSet.add(  
  55.                 new NicePlace("https://i.redd.it/obx4zydshg601.jpg",  
  56.                         "Austrailia")  
  57.         );  
  58.     }  
  59. }  
Create an Activity and name it MainActivity.java
  1. import android.arch.lifecycle.Observer;  
  2. import android.arch.lifecycle.ViewModelProviders;  
  3. import android.support.annotation.Nullable;  
  4. import android.support.design.widget.FloatingActionButton;  
  5. import android.support.v7.app.AppCompatActivity;  
  6. import android.os.Bundle;  
  7. import android.support.v7.widget.LinearLayoutManager;  
  8. import android.support.v7.widget.RecyclerView;  
  9. import android.view.View;  
  10. import android.widget.ProgressBar;  
  11.  
  12.   
  13. public class MainActivity extends AppCompatActivity {  
  14.   
  15.     private static final String TAG = "MainActivity";  
  16.   
  17.     private FloatingActionButton mFab;  
  18.     private RecyclerView mRecyclerView;  
  19.     private RecyclerAdapter mAdapter;  
  20.     private ProgressBar mProgressBar;  
  21.     private MainActivityViewModel mMainActivityViewModel;  
  22.   
  23.     @Override  
  24.     protected void onCreate(Bundle savedInstanceState) {  
  25.         super.onCreate(savedInstanceState);  
  26.         setContentView(R.layout.activity_main);  
  27.   
  28.         mFab = findViewById(R.id.fab);  
  29.         mRecyclerView = findViewById(R.id.recycler_view);  
  30.         mProgressBar = findViewById(R.id.progress_bar);  
  31.   
  32.         mMainActivityViewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);  
  33.   
  34.         mMainActivityViewModel.init();  
  35.   
  36.         mMainActivityViewModel.getNicePlaces().observe(thisnew Observer<List<NicePlace>>() {  
  37.             @Override  
  38.             public void onChanged(@Nullable List<NicePlace> nicePlaces) {  
  39.                 mAdapter.notifyDataSetChanged();  
  40.             }  
  41.         });  
  42.   
  43.         mMainActivityViewModel.getIsUpdating().observe(thisnew Observer<Boolean>() {  
  44.             @Override  
  45.             public void onChanged(@Nullable Boolean aBoolean) {  
  46.                 if(aBoolean){  
  47.                     showProgressBar();  
  48.                 }  
  49.                 else{  
  50.                     hideProgressBar();  
  51.                     mRecyclerView.smoothScrollToPosition(mMainActivityViewModel.getNicePlaces().getValue().size()-1);  
  52.                 }  
  53.             }  
  54.         });  
  55.   
  56.   
  57.         mFab.setOnClickListener(new View.OnClickListener() {  
  58.             @Override  
  59.             public void onClick(View view) {  
  60.                 mMainActivityViewModel.addNewValue(  
  61.                         new NicePlace(  
  62.                                 "https://i.imgur.com/ZcLLrkY.jpg",  
  63.                                 "Washington"  
  64.                         )  
  65.                 );  
  66.             }  
  67.         });  
  68.   
  69.         initRecyclerView();  
  70.   
  71.   
  72.     }  
  73.   
  74.     private void initRecyclerView(){  
  75.         mAdapter = new RecyclerAdapter(this, mMainActivityViewModel.getNicePlaces().getValue());  
  76.         RecyclerView.LayoutManager linearLayoutManager = new LinearLayoutManager(this);  
  77.         mRecyclerView.setLayoutManager(linearLayoutManager);  
  78.         mRecyclerView.setAdapter(mAdapter);  
  79.   
  80.     }  
  81.   
  82.     private void showProgressBar(){  
  83.         mProgressBar.setVisibility(View.VISIBLE);  
  84.     }  
  85.   
  86.     private void hideProgressBar(){  
  87.         mProgressBar.setVisibility(View.GONE);  
  88.     }  
  89. }  
Let's create an adapter and name it  RecyclerAdapter.java. The adapter is responsible to create child items in a list in our case list is of place type.
 
The adapter is associated with an XML file layout_listitems.xml
  1. public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {  
  2.   
  3.     private List<NicePlace> mNicePlaces = new ArrayList<>();  
  4.     private Context mContext;  
  5.   
  6.     public RecyclerAdapter(Context context, List<NicePlace> nicePlaces) {  
  7.         mNicePlaces = nicePlaces;  
  8.         mContext = context;  
  9.     }  
  10.   
  11.     @NonNull  
  12.     @Override  
  13.     public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {  
  14.         View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_listitem, viewGroup, false);  
  15.         ViewHolder vh = new ViewHolder(view);  
  16.         return vh;  
  17.     }  
  18.   
  19.     @Override  
  20.     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {  
  21.   
  22.         // Set the name of the 'NicePlace'  
  23.         ((ViewHolder)viewHolder).mName.setText(mNicePlaces.get(i).getTitle());  
  24.   
  25.         // Set the image  
  26.         RequestOptions defaultOptions = new RequestOptions()  
  27.                 .error(R.drawable.ic_launcher_background);  
  28.         Glide.with(mContext)  
  29.                 .setDefaultRequestOptions(defaultOptions)  
  30.                 .load(mNicePlaces.get(i).getImageUrl())  
  31.                 .into(((ViewHolder)viewHolder).mImage);  
  32.     }  
  33.   
  34.     @Override  
  35.     public int getItemCount() {  
  36.         return mNicePlaces.size();  
  37.     }  
  38.   
  39.     private class ViewHolder extends RecyclerView.ViewHolder{  
  40.   
  41.         private CircleImageView mImage;  
  42.         private TextView mName;  
  43.   
  44.         public ViewHolder(@NonNull View itemView) {  
  45.             super(itemView);  
  46.             mImage = itemView.findViewById(R.id.image);  
  47.             mName = itemView.findViewById(R.id.image_name);  
  48.         }  
  49.     }  
  50. }  
MainActivityViewModel.java
  1. public class MainActivityViewModel extends ViewModel {  
  2.   
  3.     private MutableLiveData<List<NicePlace>> mNicePlaces;  
  4.     private NicePlaceRepository mRepo;  
  5.     private MutableLiveData<Boolean> mIsUpdating = new MutableLiveData<>();  
  6.   
  7.     public void init(){  
  8.         if(mNicePlaces != null){  
  9.             return;  
  10.         }  
  11.         mRepo = NicePlaceRepository.getInstance();  
  12.         mNicePlaces = mRepo.getNicePlaces();  
  13.     }  
  14.   
  15.     @SuppressLint("StaticFieldLeak")  
  16.     public void addNewValue(final NicePlace nicePlace){  
  17.         mIsUpdating.setValue(true);  
  18.   
  19.         new AsyncTask<Void, Void, Void>(){  
  20.             @Override  
  21.             protected void onPostExecute(Void aVoid) {  
  22.                 super.onPostExecute(aVoid);  
  23.                 List<NicePlace> currentPlaces = mNicePlaces.getValue();  
  24.                 currentPlaces.add(nicePlace);  
  25.                 mNicePlaces.postValue(currentPlaces);  
  26.                 mIsUpdating.postValue(false);  
  27.             }  
  28.   
  29.             @Override  
  30.             protected Void doInBackground(Void... voids) {  
  31.   
  32.                 try {  
  33.                     Thread.sleep(2000);  
  34.                 } catch (InterruptedException e) {  
  35.                     e.printStackTrace();  
  36.                 }  
  37.                 return null;  
  38.             }  
  39.         }.execute();  
  40.     }  
  41.   
  42.     public LiveData<List<NicePlace>> getNicePlaces(){  
  43.         return mNicePlaces;  
  44.     }  
  45.   
  46.   
  47.     public LiveData<Boolean> getIsUpdating(){  
  48.         return mIsUpdating;  
  49.     }  
  50. }  
We call a method from view to create another child item in a list named  "washington". ViewModel now in turn call model and get data in return and that data is being observed by activity. ViewModel sends data to activity through observable and data gets updated.
 
Output

MVVM Architecture with LiveData Android

Now we can see a "+" button at the extreme right. Now when it gets clicked, the method triggered is shown below:
  1. mFab.setOnClickListener(new View.OnClickListener() {  
  2.            @Override  
  3.            public void onClick(View view) {  
  4.                mMainActivityViewModel.addNewValue(  
  5.                        new NicePlace(  
  6.                                "https://i.imgur.com/ZcLLrkY.jpg",  
  7.                                "Washington"  
  8.                        )  
  9.                );  
  10.            }  
  11.        });  
A new entry in the end gets added, shown below:

MVVM Architecture with LiveData Android

Finally,, we can see the entry after loading gets complete.

MVVM Architecture with LiveData Android

That's all folks. We have a final entry of Washington in our list. I hope you enjoyed this article on MVVM with LiveData.