Introduction

With the release of Android Studio 1.3, announced at Google I/O 2015, the Data Binding Library was introduced. The Data Binding Library allows you to write declarative layouts and minimize the glue code necessary to bind your data and layouts. More information about this library can be found in the Data Binding Guide.

Essentially, this library allows you to bind values of an object directly to views in your layout, and if the object changes, the view is updated automatically. The library also introduces some functionality that can make referencing views in your layout easier and faster, and in some cases unnecessary altogether.

This post will walk you through how to implement the Data Binding Library in your application.

App Setup

To use the Data Binding Library in an Android project the following dependencies need to be added to your classpath in the top level build.gradle file. Since the release of the Android Gradle Plugin 1.3.1, this can be used in place of the 1.3.0-beta4 version of the plugin.

dependencies {
 classpath "com.android.tools.build:gradle:1.3.0-beta4"
 classpath "com.android.databinding:dataBinder:1.0-rc1"
}

The Data Binding plugin must then be applied to your app module build.gradle file after the application plugin.

apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'

The app now has the necessary dependencies to enable data binding.

Simple Data Binding

The basic usage pattern is using the Data Binding Library to simply bind strings from your model object to your view. This is certainly the safest way to use binding in your app. To enable data binding for your layout, simply wrap your existing elements in a layout element.

The model object is specified inside of the data element. This will add a setter for the "user" object on the generated binding class. Since the binding classes are generated based on your layout they will be named based on the layout file name. Values can be bound to attributes using an expression specific to the Data Binding Library. These expressions are essentially java statements with some helpful operators to make things easier. In the example above the user object's firstName and lastName fields are accessed. This does not mean that the fields need to be public as this will actually trigger a call to the setters for those fields if they are declared as private.

To access the binding within an activity you inflate your layout using the DataBindinUtil class method setContentView() instead of the version provided by the Activity. This method returns a ViewDataBinding instance specific to the layout inflated. In the example app included with this post, the layout is inflated from within a fragment.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
 FragmentSimpleBindingBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_simple_binding, container, false);

 user = new SimpleBindingUser();
 user.setFirstName(getString(R.string.app_name));
 binding.setUser(user);

 return binding.getRoot();
}

Notice that the root view of the inflated layout is returned in onCreateView obtained from the ViewBinding instance. The only method that needs to be called on the binding class is the setUser method, and now this instance is tied to the UI.

Simple Two-way Data Binding

There are times when your object may change and you would like the UI to reflect those changes immediately. Having the User class extend the BaseObservable class, the binding instance can be notified about changes to the object.

public class SimpleBindingTwoWayUser extends BaseObservable {
 private String name;

 @Bindable
 public String getName() {
 return name;
 }

 public void setName(String name) {
 this.name = name;
 notifyPropertyChanged(BR.name);
 }
}

Notice the call to notifyPropertyChanged(), this informs the binding class that this specific value has changed and the bound view or views should be updated. The BR class has generated IDs that are tied to the fields in the model. Anything annotated with @Bindable in the model class will have an ID in the BR class to be used for notifications. This allows the binding class to update only specific views rather than every view related to the object. If this is desired due to computed getters the notification can be sent for BR.user instead of the field that changed. The Data Binding Library also provides the ObservableField class and some sub-classes, that can simply wrap a field if the model object is not able to extend BaseObservable.

Simple RecyclerView DataBinding

Data binding can also be used within a ListView or RecyclerView. The ViewDataBinding instance returned from inflating a layout with the DataBindingUtil is perfect when used as a ViewHolder. The implemented ViewHolder only needs a reference to the ViewDataBinding instance.

private class BindingViewHolder extends RecyclerView.ViewHolder {
 public BindingViewHolder(View itemView) {
 super(itemView);
 }

 public ViewDataBinding getBinding() {
 return DataBindingUtil.getBinding(itemView);
 }
}
@Override
public void onBindViewHolder(BindingViewHolder holder, int position) {
 SimpleUser user = users[position];
 holder.getBinding().setVariable(BR.user, user);
 holder.getBinding().executePendingBindings();
}

Rather than setting all the views in onBindViewHolder, only the user object needs to be set and the Data Binding Library will handle updating all the necessary views. The one difference to note here is that the bindings need to be executed immediately rather than being deferred to the next frame, so that requires a call to executePendingBindings() to ensure the UI stays up-to-date.

Two-way RecyclerView DataBinding

While probably not a real use case, there could be an instance where the same item appears in your list in multiple places or the same object has multiple representations in the same list. In the provided example app, every item in the RecyclerView is bound to the same object. When that object updates every visible item in the list updates.

@Override
public void onBindViewHolder(BindingViewHolder holder, int position) {
 holder.getBinding().setVariable(BR.user, user);
 holder.getBinding().executePendingBindings();
}

Data Binding Expressions

Thus far, the examples have been simply placing text into the UI. However, values can be bound to any view attribute. In the example below the user object also specifies a color to be used as the view's background.

One thing to note in this example, TextView does not contain an attribute for backgroundColor but it does have a setter for backgroundColor. The Data Binding Library will use the custom attribute name and call the setter or binding adapter for the attribute if present. When using custom attributes be sure not to use the android namespace or you will get an error. In the example the namespace: xmlns:app="http://schemas.android.com/apk/res-auto" is used.

If there is a situation where a view needs to alter its appearance based on a value, an expression can be used to change a property of that view. In this example the font size is based on the user's age.

Also, there may be cases where you want a default value to be present in the absence of a value. The null coalescing operator (??) can be used in an expression to provide a default value if the accessed property returns null. In this example if the user does not have an age we can use a default value to set the text size. In the example below age is a not a primitive but rather an instance of the class Integer. The reason for this is the Data Binding Library will use the field's default value when not set, in the case on an int that would be 0, so an Integer was used instead.

The common comparison and mathematical operators are also available within expressions, return values of method calls can also be used, for the full list of options available in expressions please refer to the Data Binding Guide. While there's quite a bit of logic that can be placed into your layouts, I recommend keeping the binding expressions as simple as possible. With limited IDE integration your expressions may not update when classes and methods are refactored, and at the moment a clean is needed if a layout containing a binding is renamed.

Binding Adapters

A really interesting feature of the Data Binding Library is using custom attributes to bind values to your view. In the example below the attribute "imageUrl" is used to load an image from the network into an ImageView. The "imageUrl" attribute is not actually an attribute of an ImageView, but a binding adapter can be specified for that attribute. The Data Binding Library provides binding adapters for the system supported view attributes. In the "imageUrl" binding adapter the Picasso library is used to trigger a download of the specified image.

@BindingAdapter("imageUrl")
public static void loadImage(ImageView imageView, String imageUrl) {
 Picasso.with(imageView.getContext()).load(imageUrl).into(imageView);
}

There may even be cases where you need access to multiple attributes in a single binding adapter. In the example below, the "imageUrl" attribute is passed in as well as a "placeholder" attribute which references a drawable which will be used if the "imageUrl" is null or an error occurs during the image download.

@BindingAdapter({"imageUrl", "placeholder"})
public static void loadImage(ImageView imageView, String imageUrl, Drawable drawable) {
 Picasso.with(imageView.getContext()).load(imageUrl).placeholder(drawable).into(imageView);
}

Features

Bonus Feature

A bonus feature not mentioned thus far is that the Data Binding Library can be used to replace libraries like ButterKnife for grabbing references to views in your layout. Butterknife generates the findViewById() calls for you through annotation processing. If views in your layout have an id, the generated binding class will have a field for those views. During the layout inflation by the DataBindingUtil class the view hierarchy is traversed once and references to necessary view are grabbed. While this is likely to have no noticeable impact on your application, the view hierarchy will not be traversed once per view, but rather once in total for view references. Even better: your Activity or Fragment will not even have to declare variables for all these views as the ViewBinding instance will hold references to all the views.

Performance

The Data Binding Library avoids the use of reflection and generates binding classes at compile time for layouts you indicate will use data binding. Also, the view hierarchy is only traversed once to find your views.

Backwards Compatible

During the build process the binding specific elements are removed from the layout and replaced with generated tags, allowing usage all the way back to Android 2.1 (API level 7+).

Efficient View Referencing

Probably the most interesting feature, is the library's traversal of the view hierarchy. The library is a bit smarter about grabbing references to your views; it does not generated calls to findViewById() for you like the ButterKnife library. It traverses the entire view-hierarchy once grabbing references to views you bound values to. As an added bonus, any view that has an id will have a field in the generated binding class removing the need to call findViewById() for any view in your layout you need a reference to. The binding class can be thought of as an intelligent view holder.

Null Safety

If the bound object or fields have a null value, it fails gracefully. Trying to access the name field on a null object will just send a null value to your view. The library uses the default value of the field based on its type, in the case of a String this is null. This can clean up the large amount of null checks needed for a deeply nested object.

Observable Fields

If your object is unable to inherit from BaseObservable because it already has a parent, you can simply change your object's fields to use the ObservableField types provided by the library in the place of your primitives or Parcelables.

Binding Adapters

Binding adapters allow you to set any property of a view including custom attributes you create. For example, if you have an ImageView you can set a custom attribute of imageUrl and use your BindingAdapter method to use the value to create a network call to load the image into the ImageView.

Conclusion

The Data Binding Library introduces improved functionality to developers; the highlight I see is the removal of boilerplate code. Glue code for your data and views could be removed; at the least, the long list of view references in an activity or fragment can be removed and replaced by a single reference to a ViewDataBinding object. One thing to keep in mind when using this library, is deciding what logic should be in the layout, if any. Simple expressions like inserting text from an object make sense, but the decision of whether a view is visible or gone is probably not a good idea, however it is possible. I would likely only bind values to views in the simplest of use cases, while in others I would just use the Data Binding Library to have references to all my views without having to call findViewById() for each view. The example app for this post can be found on Github.