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:
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.
Instantiate the UI Data model inside your Activity or Fragment:
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,