Two-Way Data binding in Android

Two-way data binding is a way to update your Views attributes using Observable objects and vice-versa. In Android framework you would essentially define an observable object which would notify your views (e.g EditTexts) whenever it is updated. The term “Two-way” means that whenever your views are updated, your observable object would also get updated. This design pattern allows for loose coupling between your Android layout and your Activities/Fragments. It is often used in MVVM pattern.

Without further due, let’s develop our data binding app. By default, data binding is not enabled in a new project. Let’s start with enabling data binding:

Update build.gradle for app module (Module: app)

Inside of “android {} ” tag add the code:

dataBinding {
    enabled = true
}

 

The result build.gradle (Module: app) should be as below  :

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "devanshapps.databindingexampleapp"
        minSdkVersion 17
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    dataBinding {
        enabled = true
    }
}

Note enabling data binding will actually import the android.databinding support library 

 

Our App Design

For this example, we will have a simple design with a list of EditText inputs as below:

Screen Shot 2018-10-06 at 8.49.05 PM.png

 

Enabling Data Binding in a layout file

To enable data binding you would need to update your root/parent view to “layout” tag:

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <android.support.constraint.ConstraintLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <EditText
            android:id="@+id/edt_firstname"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:ems="10"
            android:inputType="textPersonName"
            android:text="First Name"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

         .
         .
         .
    </android.support.constraint.ConstraintLayout>
</layout>

 

Accessing views from Java

You will first need to “Build” your project so that the required “data binding” classes get generated. In our case, since our layout file name is “activity_main.xml”, the class ActivityMainBinding will get generated.

In our MainActivity.class, you will need to replace “setContent(R.layout.activity_main);” with the generated ActivityMainBinding. The result will be as below:

public class MainActivity extends AppCompatActivity {
    ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        binding = 
          DataBindingUtil.setContentView(this, R.layout.activity_main);
    }
}

If all is setup properly, you should be able to access EditTexts defined from “binding” object itself as below:

private void setViews() {
    binding.edtFirstname.setText("Bob");
    binding.edtSurname.setText("Dylan");
}

Note: We are not there yet with regards to our actual two-way data binding!

 

Create an Observable class for UI data fields:

Our observable object needs to extend “BaseObservable” from data binding library “android.databinding.BaseObservable

First we will add all of the required fields in our layout file:

public class User extends BaseObservable {
    private String firstName;
    private String surname;
    private String jobTitle;
    private String hobbits;
}

 

Add a reference to the User data object in the layout file:

In our activity_main.xml we will now need to add a reference to the User class as below:

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <variable
            name="data"
            type="devanshapps.databindingexampleapp.User"/>
    </data>
    
    <android.support.constraint.ConstraintLayout
    .
    .
</layout>

 

We will also need to add a reference to the attributes e.g firstName, surname, jobTitle, hobbits:

<EditText
    android:id="@+id/edt_firstname"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginEnd="8dp"
    android:layout_marginStart="8dp"
    android:layout_marginTop="8dp"
    android:ems="10"
    android:inputType="textPersonName"
    android:text="@={data.firstName}"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<EditText
    android:id="@+id/edt_surname"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginEnd="8dp"
    android:layout_marginStart="8dp"
    android:layout_marginTop="12dp"
    android:ems="10"
    android:inputType="textPersonName"
    android:text="@={data.surname}"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.0"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/edt_firstname" />

<EditText
    android:id="@+id/edt_job_title"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginEnd="8dp"
    android:layout_marginStart="8dp"
    android:layout_marginTop="16dp"
    android:ems="10"
    android:inputType="textPersonName"
    android:text="@={data.jobTitle}"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.0"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/edt_surname" />

<EditText
    android:id="@+id/edt_hobbits"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginEnd="8dp"
    android:layout_marginStart="8dp"
    android:layout_marginTop="12dp"
    android:ems="10"
    android:inputType="textPersonName"
    android:text="@={data.hobbits}"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.0"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/edt_job_title" />

 

Getters and Setters for data binding object

Note that we want to have two observers in our case; our User object observing the Views (activity_main.xml) and the Views observing the User object in java.

We will need to add the annotation “@Bindable” to the “Getter” method which is from the data binding library (“android.databinding.Bindable”). This will allow our layout file to get the value of the property  by using the getter methods.

After adding the “@Bindable” annotation to all of the fields’ Getters, build the project. This will generate BR.firstName, BR.surname, BR.jobTitle, BR.hobbits).

Call the method notifyPropertyChanged(BR.field_name) inside of the setter methods so that the views are now notified whenever a field in the user object updated. The resulting user class will look like below:

public class User extends BaseObservable {
    private String firstName = "";
    private String surname = "";
    private String jobTitle = "";
    private String hobbits = "";

    @Bindable
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }

    @Bindable
    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
        notifyPropertyChanged(BR.surname);
    }

    @Bindable
    public String getJobTitle() {
        return jobTitle;
    }

    public void setJobTitle(String jobTitle) {
        this.jobTitle = jobTitle;
        notifyPropertyChanged(BR.jobTitle);
    }

    @Bindable
    public String getHobbits() {
        return hobbits;
    }

    public void setHobbits(String hobbits) {
        this.hobbits = hobbits;
        notifyPropertyChanged(BR.hobbits);
    }
}

Cool. We are almost done. Note that we have to add the “@Bindable” annotation to the Getter method first so that the associated “BR” fields are generated.

 

Insert the User Model inside of Activity (or fragment) class:

Inside of our MainActivity, we will instantiate the User object and pass it on the the DataBinding (binding) object:

public class MainActivity extends AppCompatActivity {
    ActivityMainBinding binding;
    User data = new User();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setData(data);
    }
}

 

Done!

You should  now able to reflect any changes from the the User object to the Views and any user inputs to reflect into the User Object (UI model). This allows our Activity (or Fragment) to manage view data directly from the UI object with little coupling with the View (layout file). For instance, the MainActivity does not need to know if “firstName” is an EditText or a TextView (type of view), changing the EditText to TextView will not require any change inside of MainActivity or User Object Model.

I have added the following code in the example project to showcase the two-way data binding in action:

private void setViews() {
    data.setFirstName("Bob");
    data.setSurname("Dylan");
}

public void printData(View view) {  // onclick on "Print" button listener
    Log.i(MainActivity.class.toString(),
            "User data: "  + data.getFirstName() + " " + data.getSurname()
                    + " works as " + data.getJobTitle() + " and likes to " + data.getHobbits());

}

 

Basically the Activity will load with the fields “first name” and “surname” set to “Bob” and “Dylan”. Also I have added button “Print” which will get updated values of EditTexts from the User object after the user has changed fields values. The complete source code for the example project is available on github: https://github.com/devansh-ramen/Two-Way-DataBinding-Android

That will be it for this post on “Two-way” data binding. There are few additional tweaks which may be are required for e.g, to use “Spinner” views or to add String Utils method (e.g formatting values) displayed in views. But this might be a good topic for another blog post. We have covered the basic to get started and running with the two-way data binding. This should already help to make our code cleaner and faster to debug/develop. As always happy coding. You can always let me know if you are getting stuck in some related issues.

Cheers.

Regards,

 

Please Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s