I have been using MVVM pattern for a while now, and I am loving it. Here we will look into how to build MVVM pattern using data binding and RxJava.
In this tutorial we will refactor our previous example of login screen to follow MVVM pattern.
In previous example you might have noticed every edit field is associated to an observable and these observables need to be explicitly disposed once not in use. We can eliminate some of these by using the following approach. Also brings up benefits of MVVM pattern.

Basics of MVVM

MVVM pattern has three main players, Model, View and ViewModel. In simple Android layman terms, view is our fragment/activity and its xml. ViewModel is a simple java class, a wrapper over Model which is easily readable/bindable by View. Binding between view and viewModel is done through data binding.

Please go through https://developer.android.com/topic/libraries/data-binding/index.html if you are not familiar with android data binding. In data binding each interested field in UI is represented through an object called ObservableField. This enables us to reference that field through viewModel class directly. This will make the Fragment/Activity code very plain and simple. ViewModel will take care of visualizing the screen.

Steps involved

1. Create ViewModel
2. Create layout XML and reference viewModel from it.
3. Create Fragment. Bind layout and viewModel together.
4. Attach Model to ViewModel class.

Create ViewModel

Lets begin my creating viewModel first. Add this line to your apps build.gradle file, to support databinding.

 dataBinding { enabled = true } 

We have 4 userInteractive fields in login screen. Username, Password, Email and Login button. So lets create 4 Observable fields and 3 more for error texts.

 public class LoginViewModel { public ObservableField<String> userName = new ObservableField<>(); public ObservableField<String> password = new ObservableField<>(); public ObservableField<String> email = new ObservableField<>(); public ObservableField<String> userNameErr = new ObservableField<>(); public ObservableField<String> passwordErr = new ObservableField<>(); public ObservableField<String> emailErr = new ObservableField<>(); public ObservableField<Boolean> enableLogin = new ObservableField<>(); public LoginViewModel() { enableLogin.set(false); } } 

If you remember we used combineLatest operator to look for changes in input fields and enable login button. combineLatest operator need observables to operate on. So there has to be a utility method which converts ObservableField to Observable.

 @NonNull public static <T> Observable<T> toObservable(@NonNull final ObservableField<T> field) { return Observable.create(new ObservableOnSubscribe<T>() { @Override public void subscribe(final ObservableEmitter<T> emitter) throws Exception { T initialValue = field.get(); if (initialValue != null) { //Emit initial value emitter.onNext(initialValue); } final OnPropertyChangedCallback callback = new OnPropertyChangedCallback() { @Override public void onPropertyChanged(android.databinding.Observable observable, int i) { //Emit value whenever there is change in observableField emitter.onNext(field.get()); } }; field.addOnPropertyChangedCallback(callback); emitter.setCancellable(new Cancellable() { @Override public void cancel() throws Exception { //Remove property change listener when observable is no longer required field.removeOnPropertyChangedCallback(callback); } }); } }); } 

Now apply combine latest operator on input fields.

 Observable.combineLatest(FieldUtils.toObservable(userName), FieldUtils.toObservable(password), FieldUtils.toObservable(email), new Function3<String, String, String, Boolean>() { @Override public Boolean apply(String userName, String password, String email) throws Exception { int failCount = 0; if (!InputValidator.validateUserName(userName)) { ++failCount; userNameErr.set("Username format not correct"); } else { userNameErr.set(""); } if (!InputValidator.validatePassword(password)) { ++failCount; passwordErr.set("Password format not correct"); } else { passwordErr.set(""); } if (!InputValidator.validateEmail(email)) { ++failCount; emailErr.set("Email format not correct"); } else { emailErr.set(""); } return failCount==0; }}) .subscribe(new Observer<Boolean>() { @Override public void onSubscribe(Disposable d) { mCompositeDisposable.add(d); } @Override public void onNext(Boolean aBoolean) { enableLogin.set(aBoolean); } @Override public void onError(Throwable e) { } @Override public void onComplete() { } }); 

Create Layout XML

Previous Login fragment XML is refactored to accommodate data binding. LoginViewModel is bound to XML using variable name viewModel.

 <data>     <variable         name="viewModel"         type="com.jacksvarghese.rxsamples.LoginViewModel"/> </data> 

Observable fields are accessed in XML through the viewModel object. For eg.

 <android.support.design.widget.TextInputEditText</span>                       android:layout_width="match_parent"                       android:layout_height="wrap_content"                       android:hint="User Name"                       android:text="@={viewModel.userName}"/> 

Create fragment.

OnCreateView() is slightly different when using databinding.

 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {     // Inflate the layout for this fragment     FragmentLoginBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_login, container, false);     //setViewModel method name changes based on variable name declared in XML      binding.setViewModel(new LoginViewModel());     return binding.getRoot(); } 

Layout is inflated and LoginViewModel object is assigned to the viewModel variable we declared previously in XML.

What Next ?

Source code of this example is available at: https://github.com/jacksvarghese86/RxJavaIntro. 

In ViewModel we still have a subscription which needs to be explicitly unsubscribed after screen is destroyed. We will see how to do this implicitly by view itself, so that we don’t have to explicitly call dispose method on subscriptions. Also will see how to handle click actions on button in viewModel.

Jacks Varghese
Over 7+ Years of Experience in Design and development in embedded and Mobile application. Expertise and Executed multiple Android SDK applications for STB (Set Top Box), Google TV, and Mobile Devices.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.