Android Two-Way Binding With ViewModel

http://justmobiledev.com/wp-content/uploads/2018/12/graffitti-android2.jpghttp://justmobiledev.com/wp-content/uploads/2018/12/graffitti-android2.jpghttp://justmobiledev.com/wp-content/uploads/2018/12/graffitti-android2.jpgAndroid Two-Way Binding With ViewModel

Intro

Two-Way binding is awesome! If I had my way, I’d use two-way binding on every page. As if one-way binding wasn’t great enough, two-way binding is having it both ways. 😂

What is Two-Way Binding?

So what is two-way binding? In this approach, the UI controls of the view are being bound to a property of a model, which holds our data. Updates from the view by the user that need to be reflected in the model are automagically handled by the framework. Analogous, updates to the model by the business logic (or controller) that should be reflected in the view, are also being automatically handled.

What’s not to like? UI controls and Models are kept in sync with very little code. You keep your controller code as clean as Lindsay Lohan fresh out of rehab and, best of all, it’s less work for you since you don’t have to spend your life writing code to sync the UI with your data and can spend your time with far more important stuff, like – uhm – refactoring some snippets you wrote in a caffeine-depleted daze that make the happy path look like a sad joke, and where neither the t’s are crossed nor the i’s are dotted.

Conventional View – Model Synchronization

As a recap and to understand how two-way binding can make our lives easier, let’s review how a we would keep the UI in sync with our data in the conventional approach.

The way I’ve seen it being implemented often is that for every UI control, a variable is defined in the Activity or Fragment. The variable is bound to the view using the findViewById() method.


1
EditText myEditText = findViewById(R.id.my_edittext);

Next, when data is loaded from the database or a cloud source, we need to update the UI.


1
myEditText.setText(myData);

When we want to retrieve changes from the UI control and update our data, we need to get the data from the control. And then we can store the data in our repository. Alternatively, we could define a text change listener that updates our data immediately every time the text changes.


1
String myData = myEditText.getText();

It may not seem like much, but if your app has twenty pages and you have ten controls each, that’s 600 lines of code you have to crank out. So there must be a better way.

Options, Options

If you wanted to implement two-way binding in Android, there are several options:

  • Using Observables and Listeners
  • Using a Binding Operators (which we will take a look at in this post)
  • Using a third-party library like Butterknife.

Enabling Android Data Binding

In order to be able to use Android Data Binding, you have to enable it in your build gradle:


1
2
3
4
5
6
android {
    ...
    dataBinding {
        enabled = true
    }
}

Using Observables and Listeners

Let’s take a look at how we would implement two-way binding using Observables and Listeners.

Let’s say I have a simple form with one edit text field called for a user name:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="viewModel"
            type="com.company.FormViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
            <EditText
                android:id="@+id/edittext_user_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@{viewModel.userName}" />
    </LinearLayout>

Let’s create a ViewModel class that extends from BaseObservable. In order to populate the EditText field from my data, I expose a setUserName() method in the ViewModel that sets the VM variable and notifies the View that the data has changed using notifyPropertyChanged().


1
2
3
4
5
6
7
8
9
public class UserViewModel extends BaseObservable {

    private String userName;

    public void setUserName(String userName) {
        this.userName= userName;
        notifyPropertyChanged(BR.userName);
    }
}

In order to implement synchronization the other way (from view to model), I can implement a textChangeListener and attach it to the EditText Control:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="viewModel"
            type="com.company.FormViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
            <EditText
                android:id="@+id/edittext_user_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@{viewModel.userName}"
          app:textChangedListener="@{viewModel.userNameTextWatcher}" />
    </LinearLayout>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Bindable
public TextWatcher getUserNameTextWatcher() {
    return new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // Do nothing.
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            setUserName(s.toString());
        }

        @Override
        public void afterTextChanged(Editable s) {
            // Do nothing.
        }
    };
}

In order for this approach to work, we must also create a binding adapter, which defines how a text watcher can be bound to an EditText control:


1
2
3
4
5
6
7
public class EditTextBindingAdapters {

    @BindingAdapter("textChangedListener")
    public static void bindTextWatcher(EditText editText, TextWatcher textWatcher) {
        editText.addTextChangedWatcher(textWatcher);
    }
}

As you can see this approach is not very elegant and requires a lot of overhead code. So on to the next approach.

Using Binding Operators

Instead of having to create an XML attribute to bind a listener, you can take advantage of Data Binding’s built-in support for two-way bindings.


1
2
3
4
5
6
7
8
public class UserViewModel extends BaseObservable {
    private String userName;

    public void setUserName(String userName) {
        this.userName= userName;
        notifyPropertyChanged(BR.userName);
    }
}

1
2
3
4
<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={viewModel.userName}" />

Note the’ =’ in the Data Binding operator. This will cause the EditText to be populated with the value of the ‘userName’ variable from the ViewModel but the addition of the ‘=’ tells the generated Binding class to call userName variable’s setter method whenever the text changes.

As you can see this implementation is much cleaner and compact.

Taking it a step further: The ViewModel class

One of the issues developers face every day is holding on to user UI changes while the associated Activities and Fragments move through their life cycles. For example, when the device is rotated, the system might decide to recreate an activity or a fragment. As a result, user changes may be lost. This is the problem that the Android ‘ViewModel’ class tries to addresss.

The ‘ViewModel’ class has been around since Android version 1.0.0 and I’m surprised it is not used more often. The ViewModel is persistent even when it’s owner is destroyed and recreated as a result of a configuration change. In other words, you don’t have to worry about saving and restoring user data.


1
2
3
4
5
6
7
8
9
10
11
public class UserNameViewModel extends ViewModel {
    private MutableLiveData<String> userName= new MutableLiveData<>();

    public void setUserName(String userName){
        this.userName.setValue(userName);
    }

    public LiveData<String> getUserName(){
        return userName;
    }
}

Observable Properties using LiveData

You may have noticed the ‘MutableLiveData’ property in the method above. It is a sub-class of ‘LiveData’ which is another Android observable class.

Unlike observable, LiveData is also life-cycle aware, which means it only notifies observers about updates when the associated Fragment or Activities is in ‘Started’ or ‘Resumed’ state.

LiveData and ViewModel work well together to transparently handle life-cycle and configuration changes.

Two-Way Binding using ButterKnife

ButterKnife is an open-source Android binding library available on GitHub and licensed under Apache 2.0.

It has the same intention as the Android standard binding functionality, which is to make synchronization between view and data as convenient as possible.

To review ButterKnife is out of the scope of this post, but there are lots of good ButterKnife tutorials out there, e.g this one from Vogella.

Personally, as a security professional I always tend to prefer standard framework options over third-party libraries for the reasons of compatibility, stability, and security so I usually stick with Android Data Binding.

Recap

In this post we discussed the importance of two-way binding in order to create clean, maintainable apps, we reviewed the conventional way of synchronizing UI controls with data variables using findViewById().

Then we looked at implementing two-way binding using Observables and Listeners and finally, in my view the most powerful approach using ViewModel, LiveData and Binding Operators.

Sample Implementation

For a working sample project with ViewModel, LiveData and two-way binding implementation, please check out my GitHub repository here:

https://github.com/justmobiledev/android-two-way-binding-1

Author Description

justmobiledev

There are 2 comments. Add yours

  1. 25th February 2019 | Sunm says: Reply
    Great article explaining two way binding. Keep em coming.
  2. 26th February 2019 | Yzha says: Reply
    Great introduction to Android two-way data binding! Just in time, we are about to refactor our project at work to leverage the native data binding capability. This post gave me a jump start. Thank you!

Join the Conversation