Two-way Android Data Binding

How to use two-way Data Binding to manage a layout


EDIT: At droidcon London 2016 I made a talk about Data Binding, the video is available here and the slides here. It contains an updated version of the examples of this post.

One of the most interesting news for Android developers presented at Google I/O 2015 is the Data Binding framework. It has been discussed for quite a long time, now the moment has arrived to test this framework in a real example.

In the official website and on many blogs there are examples of how to use data binding in Android apps, many of these examples leverage the framework features. For these reason they highlight well the points of strength of the framework. In this post we will start from two real examples (one trivial and another more complex) to see how the Data Binding could be useful for developing Android apps.

EDIT: since Android plugin 2.1 alpha 3 contains official two way data binding support. The solution in this post is still valid but it’s deprecated, you should use official solution.

Two-way binding in a text field

Echo is the classic data binding example: two text fields bound to the same field of a bean. It allows to verify that what we are writing in a field is been updated in the other field in real time. In order to write this example let’s start with a simple Java class with a text field:

public class Echo {
public String text;
}

To make this example easy this class doesn’t contain getters and setters (we could even talk for days about the fact that many Android developers don’t use the getters but… let’s forget about it!). Even if the bean would contains getters and setters, it didn’t change the whole example.

The layout is really simple and it is made up of two EditText with the same binding in the text attribute:

<layout 
xmlns:android=”http://schemas.android.com/apk/res/android"
xmlns:tools=”http://schemas.android.com/tools">
  <data>
<variable
name=”echo”
type=”it.cosenonjaviste.databinding.echo.Echo”/>
</data>
  <LinearLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
android:padding=”16dp”>
    <EditText
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:hint=”Text 1"
android:text=”@{echo.text}”/>
    <EditText
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:hint=”Text 2"
android:text=”@{echo.text}”/>
  </LinearLayout>
</layout>

In the Activity you need to set the layout using the DataBindingUtil class and then the object on which you make the binding (the class EchoBinding is automatically generated according to the layout):

public class EchoActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EchoBinding binding = DataBindingUtil.setContentView(
this, R.layout.echo);
binding.setEcho(new Echo());
}
}

Alright, in an ideal world this example should work by just putting the right listeners on the bean and on the Views and then update everything… but in the real world when executing this example, the listeners are not added and writing in one of the text field doesn’t automatically update the other one. The only working binding in this scenario is the initial one between the string in the Echo class and the two text fields. When the setEcho method is called on the EchoBinding object, if the field is not empty, this one will be set on the two fields in the graphic interface. Binding works only from the object to the layout (and not vice versa) and only when the object is set using the setEcho method. Further changes in the value of the object field are not reflected in the layout.

In order to receive the next updates automatically it is necessary to define the field using a ObservableField<String> and not simply using a string:

public class Echo {
public ObservableField<String> text = new ObservableField<>();
}

The class ObservableField (and the other classes for the native types such as ObservableInt and ObservableBoolean) could be seen as an implementation of the classical Observable pattern (to get it better, it is not similar to the Observables managed by RxJava). Practically, when the method setEcho is called on the EchoBinding object, a listener on the Observable is registered. This listener is invoked on every update and it will update the Views contained in the layout.

Adding a listener to the views in the layout using the Data Binding is not trivial, you need to use a TextWatcher. The only advantage you have using the Data Binding is the possibility to add the TextWatcher in the layout:

<EditText
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:hint=”Text 1"
android:text=”@{echo.text}”
android:addTextChangedListener=”@{echo.watcher}”/>

In this way it is necessary to define the TextWatcher field inside the Echo class:

public class Echo {
public ObservableField<String> text = new ObservableField<>();
  public TextWatcher watcher = new TextWatcherAdapter() {
@Override public void afterTextChanged(Editable s) {
if (!Objects.equals(text.get(), s.toString())) {
text.set(s.toString());
}
}
};
}

The check within the event was added to avoid infinite cycles: the layout that updates the object calls the listener to update the graphic interface.

In this way the two-way binding works properly but there are a couple of things that worry me a bit:

  • the Echo class should be disjointed from the graphic interface, the idea of using an ObservableField is not really the cleanest way to execute it and the need to define a TextWatcher within is not really the best either;
  • for each field on which the binding must be executed, it is necessary to write a lot of code (two fields linked to each other).

Custom binding definition

In order to solve the mentioned problems, we can use some custom bindings, the Data Binding framework allows us so define them very easily. First, we must define one of our objects similar to ObservableField but string-specific (let’s also change its name as to avoid confusion with the original class and RxJava’s Observables):

public class BindableString extends BaseObservable {
private String value;
  public String get() {
return value != null ? value : “”;
}
  public void set(String value) {
if (!Objects.equals(this.value, value)) {
this.value = value;
notifyChange();
}
}
  public boolean isEmpty() {
return value == null || value.isEmpty();
}
}

While changing the class let’s also add a utility method isEmpty and also we will check if the value has changed to avoid redundant code.

In order to use this class in the attribute android:text it is necessary to write in a class a method annotated with @BindingConversion that does the conversion:

@BindingConversion
public static String convertBindableToString(
BindableString bindableString) {
return bindableString.get();
}

To simplify the above code, it is possible to create a custom attribute using the annotation @BindingAdapter. For example, we can define an attribute app:binding in which we set the value and add the TextWatcher:

@BindingAdapter({“app:binding”})
public static void bindEditText(EditText view,
final BindableString bindableString) {
Pair<BindableString, TextWatcherAdapter> pair =
(Pair) view.getTag(R.id.bound_observable);
if (pair == null || pair.first != bindableString) {
if (pair != null) {
view.removeTextChangedListener(pair.second);
}
TextWatcherAdapter watcher = new TextWatcherAdapter() {
public void onTextChanged(CharSequence s,
int start, int before, int count) {
bindableString.set(s.toString());
}
};
view.setTag(R.id.bound_observable,
new Pair<>(bindableString, watcher));
view.addTextChangedListener(watcher);
}
String newValue = bindableString.get();
if (!view.getText().toString().equals(newValue)) {
view.setText(newValue);
}
}

A couple of things to note:

  • this method is invoked every time the object on which the binding is done changes; to avoid setting more than a TextWatcher for the same text field a tag gets added (and then checked);
  • before setting the value we check the value is actually changed; this check allows to avoid problems in the position of the cursor in the text view.

Now, it is very easy to re-write the layout of the example we saw earlier, we can just use the app:binding attribute:

<EditText
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:hint=”Text 1"
app:binding=”@{echo.text}”/>

Orientation change management

A quite delicate aspect to manage in Android applications is the device rotation, also in this case we need to be quite careful. In fact, the Activity is destroyed and, in the new Activity, a new Echo object is created and the binding is redone. For this reason, it starts over, losing the content of the form already inserted by the user. :(

The right way to manage this is saving the Activity’s instance state, when creating the Echo object it is recreated only in case it is started for the first time:

public class EchoActivity extends AppCompatActivity {
  public static final String ECHO = “ECHO”;
  private Echo echo;
  @Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EchoBinding binding = DataBindingUtil.setContentView(
this, R.layout.echo);
if (savedInstanceState == null) {
echo = new Echo();
} else {
echo = Parcels.unwrap(
savedInstanceState.getParcelable(ECHO));
}
binding.setEcho(echo);
}
  @Override protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(ECHO, Parcels.wrap(echo));
}
}

In this example Parceler framework was used to simplify the management of Parcelable objects. We must also note that also the BindableString class must be mapped using Parceler; it isn’t complicated, as it’s just a matter of managing the string, as the list of listeners can be ignored when changing the orientation.

EDIT: since version 1.0-rc1 Observable classes implement Parcelable and there is also a ObservableParcelable class. For this reason definition of custom bindable classes can be avoided.

A login form using Data Binding

Let’s check out an example that is a bit more complicated, a login form similar to Amazon’s one:

The validation begins with the first submit and with every following modification, the password’s text field is only enabled when the second radio button is selected.

Let’s start from the radio buttons, the binding in this case is done on the radio group:

<RadioGroup
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:orientation=”vertical”
app:binding=”@{loginInfo.existingUser}”>
  <RadioButton
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/new_customer”/>
  <RadioButton
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/i_have_a_password”/>
</RadioGroup>

Also in this case we used a custom binding, this time with a BindableBoolean object. The binding with a Boolean is kind of forced, as it only works because there are two radio buttons, it is still possible to define in a simple way other types of binding too (for example with an int). The implementation of this class and of the binding methods definition is very similar to the one we have already seen regarding strings.

Regarding text fields, an EditText within a TextInputLayout (available in the Design Support Library) was used. In the case of a text field with password, there are three bindings to handle:

  • text: handled with a binding like in the previous example;
  • error: this is another binding toward an item such as BindableString;
  • enabled: in this case the binding is implemented with the same boolean used for the RadioGroup, so to have an automatic update every time the user selects one of the two radio buttons.

The code corresponding to this part of the layout is as follows:

<android.support.design.widget.TextInputLayout
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
app:error=”@{loginInfo.passwordError}”>
  <EditText
android:id=”@+id/password”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:enabled=”@{loginInfo.existingUser}
android:hint=”@string/password”
android:inputType=”textPassword”
app:binding=”@{loginInfo.password}”/>
</android.support.design.widget.TextInputLayout>

The binding is implemented using a LoginInfo class containing the fields used within the layout:

public class LoginInfo {
public BindableString email = new BindableString();
public BindableString password = new BindableString();
public BindableString emailError = new BindableString();
public BindableString passwordError = new BindableString();
  public BindableBoolean existingUser = new BindableBoolean();
  public void reset() {
email.set(null);
password.set(null);
emailError.set(null);
passwordError.set(null);
}
}

The reset method must be invoked upon clicking on the corresponding button, in this case I would have expected the possibility to specify the binding with a syntax android:onClick=”@{loginInfo.reset}”. Right now this is impossible, to work the reset method should return a OnClickListener containing the logic to execute. Until now I have not been able to define custom binding to obtain something similar, it’s still in beta version, we will see whether this feature will be added in one of the next versions.
We can still use the object generated by the framework to add the listener:

binding.reset.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
loginInfo.reset();
}
});

The self-generated binding object contains a field for every view present in the layout in which an id is defined, in this way is it possible to completely avoid using findViewById method! Using the Data Binding makes frameworks like ButterKnife and Holdr less useful (the latter one is already deprecated).
It is worth noting that internally the framework doesn’t use the findViewById method, in fact the code generated includes a method that searches all the views using a single visit of the layout.

EDIT: since version 1.0-rc1 a listener can be easily defined:

android:onClick="@{loginInfo.reset}"

The signature of reset method must be compatible with onClick method of OnClickListener interface: a parameter of type View. This parameter can be avoided using a custom binding:

@BindingAdapter({"app:onClick"})
public static void bindOnClick(View view, final Runnable runnable) {
view.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
runnable.run();
}
});
}

The second parameter is a Runnable, it’s just a placeholder for an interface with a single method with no parameter.
A similar way is also used to handle the listener for validation, by adding it in the Activity on the email and password fields:

TextWatcherAdapter watcher = new TextWatcherAdapter() {
@Override public void afterTextChanged(Editable s) {
loginInfo.validate(getResources());
}
};
binding.email.addTextChangedListener(watcher);
binding.password.addTextChangedListener(watcher);

Within the LoginInfo class a boolean field was added to store the fact that the user has already tried to log in. Validation occurs by checking such boolean so that it doesn’t give errors while the user is still filling in the form:

public boolean loginExecuted;
public boolean validate(Resources res) {
if (!loginExecuted) {
return true;
}
int emailErrorRes = 0;
int passwordErrorRes = 0;
if (email.get().isEmpty()) {
emailErrorRes = R.string.mandatory_field;
} else {
if (!Patterns.EMAIL_ADDRESS.matcher(email.get()).matches()) {
emailErrorRes = R.string.invalid_email;
}
}
if (existingUser.get() && password.get().isEmpty()) {
passwordErrorRes = R.string.mandatory_field;
}
emailError.set(emailErrorRes != 0 ?
res.getString(emailErrorRes) : null);
passwordError.set(passwordErrorRes != 0 ?
res.getString(passwordErrorRes) : null);
return emailErrorRes == 0 && passwordErrorRes == 0;
}

In the click listener on the login button the boolean is set and the validation is executed, if passed a message is shown to the user using the Snackbar class (available in the design support library):

binding.login.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
loginInfo.loginExecuted = true;
if (loginInfo.validate(getResources())) {
Snackbar.make(
binding.getRoot(),
loginInfo.email.get() + “ — “ + loginInfo.password.get(),
Snackbar.LENGTH_LONG
).show();
}
}
});

Conclusions

The Android framework for Data Binding is still in beta, internal support from Android Studio is still partial and there are still room for improvement. However it is very well designed and developed, and will change (in a good way!) the way Android applications are written. The possibility to define custom attributes is very powerful, I am very curious to see how it will be used in third part libraries.
The complete source code of the examples viewed of this post is available on the databinding repository on GitHub.

Data binding is the base of Model View ViewModel pattern. I am working on an Android MVVM implementation, it’s still a beta version but it’s already available on GitHub. Every feedback is welcome!

An Italian version of this post is available on cosenonjaviste.it.