Model View ViewModel MVVM Android Example

The other architectural patters which separate UI code from data code are MVP and MVC. You can see Android MVP example to learn how MVP can be implemented in android apps.

MVVM

In MVVM, model is a component which provides data and it may contain business logic or interact with business logic component.

View displays data on the screen. In android, the view is activity or fragment and their layouts.

View uses ViewModel to get data from model by binding to its properties and behavior. View and ViewModel communication using data binding framework or observer and observable framework like RxJava. View contains reference to ViewModel. ViewModel doesn’t contain reference to View.

Advantages of MVVM

The problem with MVP is that there exists tight coupling between presenter and view as presenter holds reference to view. Another disadvantage of using MVP is that, a presenter needs to be created for each activity or view.

The advantage of using MVVM pattern over MVP is that View and ViewModel are not tightly coupled as ViewModel contains no reference to View.

Unlike in MVP where view is passive and doesn’t know about model, view is active in MVVM, meaning view needs to know about model in order for it to bind to model properties exposed by ViewModel.

MVVM Example

You can learn how to implement MVVM in android by going through the following example. The MVVM example uses RxJava to implement MVVM pattern. The example displays list of categories in the list view. In response to the selected category, coupons for the selected category will be displayed in the second listview.

Activity (View)

Activity displays coupon categories obtained by listening to observable which provides categories. When user clicks a category, item click event handler passes the selected category to ViewModel. Activity obtains the coupons for the selected category by listening to observable which provides coupons for the selected category.

import android.arch.lifecycle.ViewModelProvider; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import com.zoftino.couponsmvvm.R; import com.zoftino.couponsmvvm.model.Coupon; import com.zoftino.couponsmvvm.modelview.CouponViewModel; import java.util.ArrayList; import java.util.List; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; public class CouponsActivity extends AppCompatActivity { @NonNull private CompositeDisposable compositeDisposable; @NonNull private ListView categoriesLst; @NonNull private ListView couponsLst; @NonNull private CouponViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_coupons); categoriesLst = findViewById(R.id.categories); couponsLst = findViewById(R.id.coupons); viewModel = ViewModelProvider.AndroidViewModelFactory. getInstance(getApplication()).create(CouponViewModel.class); setItemClickListener(); } private void setItemClickListener(){ categoriesLst.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { String cat = (String)adapterView.getItemAtPosition(i); //pass selected category to viewmodel viewModel.setSelectedCat(cat); } }); } @Override protected void onResume() { super.onResume(); bind(); } @Override protected void onPause() { unBind(); super.onPause(); } private void bind() { compositeDisposable = new CompositeDisposable(); //subscribe to categories observable //add the observable to disposable compositeDisposable.add(viewModel.getCategories() .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::setCategories)); //subscribe to coupons observable //add the observable to disposable compositeDisposable.add(viewModel.getCouponsByCat() .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::setCoupons)); } private void unBind() { compositeDisposable.clear(); } private void setCategories(ArrayList<String> cats){ //display received data from viewmodel ArrayAdapter<String> itemsAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, cats); categoriesLst.setAdapter(itemsAdapter); } private void setCoupons(List<Coupon> coupons){ //display received data from viewmodel ArrayAdapter<Coupon> itemsAdapter = new ArrayAdapter<Coupon>(this, android.R.layout.simple_list_item_1, coupons); couponsLst.setAdapter(itemsAdapter); } }

ViewModel

import android.arch.lifecycle.ViewModel; import com.zoftino.couponsmvvm.model.Coupon; import com.zoftino.couponsmvvm.model.CouponModel; import java.util.ArrayList; import java.util.List; import io.reactivex.Observable; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.BehaviorSubject; public class CouponViewModel extends ViewModel { //model object private CouponModel couponModel; private final BehaviorSubject<String> selectedCategory = BehaviorSubject.create(); public CouponViewModel(){ couponModel = new CouponModel(); } //categories observable public Observable<ArrayList<String>> getCategories(){ return couponModel.getCategories(); } //coupons observable emits coupons when category is selected public Observable<List<Coupon>> getCouponsByCat(){ return selectedCategory .observeOn(Schedulers.computation()) .flatMap(couponModel::getCouponsByCat); } //pass selected category to model public void setSelectedCat(String cat){ selectedCategory.onNext(cat); } }

Model

import java.util.ArrayList; import java.util.List; import java.util.Map; import io.reactivex.Observable; public class CouponModel{ private Map<String, List<Coupon>> couponsByCat; public CouponModel(){ couponsByCat = CouponData.getCoupons(); } public Observable<ArrayList<String>> getCategories(){ return Observable.just(new ArrayList<String>(couponsByCat.keySet())); } public Observable<List<Coupon>> getCouponsByCat(String cat){ return Observable.just(couponsByCat.get(cat)); } }

CouponData