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.