27.5 C
Bangalore
December 8, 2018
Untitled

MVVM Pattern using Databinding and RxJava – Part 1

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 {
    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 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<>();

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.

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;
     }})
}

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.TextInputLayout
            android:id="@+id/userNameLayout"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            app:error="@{viewModel.userNameErr}"
            app:layout_constraintBottom_toTopOf="@+id/passwordLayout"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_chainStyle="packed">
            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="User Name"
                android:text="@={viewModel.userName}"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
            android:id="@+id/passwordLayout"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            app:error="@{viewModel.passwordErr}"
            app:layout_constraintBottom_toTopOf="@+id/emailLayout"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/userNameLayout">
        <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Password"
                android:inputType="textPassword"
                android:text="@={viewModel.password}"/>
</android.support.design.widget.TextInputLayout>

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);
        mLoginViewModel = new LoginViewModel();
        //setViewModel method name changes based on variable name declared in XML
        binding.setViewModel(mLoginViewModel);
        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.

Related posts

1 comment

MVVM Pattern using Databinding and RxJava – Part 2 » Untitled September 15, 2018 at 4:02 am

[…] Part 1 we saw how to setup a MVVM pattern using RxJava and Databinding. In this lesson we will see how to […]

Reply

Leave a Reply

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

%d bloggers like this: