Data Binding Library: Great UI’s with Less code

A few months ago with the release of android 6.0 with the codename Marshmallow, alot of new things were added to this version and honestly keeping up with this new stuff while working on other projects can be a hard task. From the whole set of new API’s like fingerprint, doze, the new permissions model one of the new things that really got my attention was the Data Binding Library.

In short “The Data Binding Library helps you to write declarative layouts and minimize the glue code necessary to bind your application logic and layouts”. To make this sentence more clear I will have to go back on to how things were done before the library.

Let’s take a simple example where we want to create a simple app that receives a user name on an EditText and on the click of a button it will display that name on a Toast.

Assuming that we already passed the wizard for a new app creation on Android studio(By the way get the 2.0 version here) and we have an Activity and the layout resource file automatically generated, we would go through the following steps:

  1. Open the layout resource file for the activity, add an EditText, a Button and don’t forget to assign a unique ids to each one of this views.
  2. Go to the main activity java class and create java instances of the views.
  3. Use the findViewById method to find the views using the unique Ids for each view we defined on 1.
  4. Finally we are good to go and start playing around with that view properties in code such as getting the user text input, setting listeners etc.

This 4 steps were pretty much something we had to follow everytime we were adding or changing something on the app UI’s. At first it might look very silly to look at this 4 steps and be lazy about them when we are doing simple UI’s but by the time the UI complexity increases, the amount of boiler plate code can increase dramatically.

Going back to our sentence, the main goal of the data binding library is to cut this steps, reduce the amount of code and make it more readable.

But how does it do this?

There is nothing much to say about what’s going under the hood on how the Library works because in a high level concept it only does for us the steps I mentioned before and for more in depth understanding there is a must watch talk by @YigitBoyar explaining in details how it works during the android dev summit.

Time for some code :)

Jumping to the fun part, to get started with the data binding library we first have configure your project for it and to do it we simply add the data binding element to our app module build.gradle file like this:

dataBinding{
enabled = true
}

Sync your gradle file and then voila you are all set to start using the library on your app.

To actually start applying it on your project, there are 3 key components to look:

  1. The layout file : The layout file is the first component we have to look at because it have some changes compared to what they looked like before. In order to write UI’s that support binding, the layout file root tag that used to be normally a ViewGroup will change to the tag “layout” that will contain 2 childrens, a data tag and our regular ViewGroup with the rest of the UI code. The data tag is where we for now declare variables that we might want to use on a binding expression. To write a simple expression to an attribute we have to set the value of the attribute the following way : @{<expression goes here>} like on the code below where we set our string url variable to the image view app:loadImage attribute. The Data Binding library knows how to resolver correctly correctly when expressions are passed into common attributes such as android:text that we use to set text on an EditText or android:visibility to set a view visibility but as we are going to see later there are some cases where we need to specify how data is set ourselves.
<?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">

<data>
<variable name="imageUrl" type="String"/>
</data>

<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:layout_width="190dp"
android:layout_height="280dp"
app:imagesource="@{imageUrl}"
/>

</LinearLayout>
</layout>

2. As soon as we build our project, a binding class will be generated and its role is to provide methods to let us do the actual binding from the java code to the UI.

3. Using the binding class to do the actual binding and pass the data is the last step and this happens by creating a new instance of the binding class auto generated on the previous step and using the DataBindingUtil class to inflate the layout file that can happen in many different ways for different situations using the methods provided by this class.

A few common methods are DataBindingUtil.setContentView(R.layout) that we use when we want to directly set the activity content view and return the associated binding, DataBindingUtil.inflate(LayoutInflater, layoutId, viewgroup, boolean flag) that we can normally use when creating a fragment view. Many more methods are available on the documentation and I really advise to check them out.

We would need next to get the binding class instance and use the generated setters from the variable we defined on the data tag of the layout to bind the data to the UI. A simple code in our java file binding an activity layout to a User data object would look like this:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}

Beyond the Basics

Getting started with the data binding library is straight forward but there are lot of cool things to do. In order to get to cover some of the interesting and deep things, the remaining part of this post will be mostly about how I used the Data Binding Library to modify some of the parts of the My Movie App of the android nanodegree by Udacity.

A single Movie Poster

The first screen of the app is a simple grid with movie posters thumbnails that can be filtered by popularity,rating.

<?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">

<data>
<variable name="imageUrl" type="String"/>
</data>

<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:id="@+id/movie_thumbnail"
android:layout_width="190dp"
android:layout_height="280dp"
app:imagesource="@{imageUrl}"
/>

</LinearLayout>
</layout>

The layout above and the code are exactly the way we saw that layouts should be but the big difference here is how we set the images thumbnail to each item on the grid.
As I mentioned earlier , the library knows how to handle the data passed for some attributes because what it does on the back is just doing a set of the data to that object.
For cases like text it is pretty straight forward to just set a string but for more specific cases like loading an image to a imageview, we only have the android:src method out of the box that basically following the same analogy when using the library it provides us with a setter for a drawable which is not what we need since we normally want to load the image from an url or any other source like this case. In order to work around this, we just need to add a class and a new method, annotation it as a binding adapter and specify the name of the attribute we want to use that can be a custom name like i did or simply override an existing attribute such as android:src.
For this method signature we pass the ImageView which is the component that we want to set the image and a second parameter that is going to be the url of the image we want to load. The implementation is pretty straight forward and we simple get the context from the imageview and using picasso(I use it a lot) or other image loader library we start loading the image to the imageview.

@BindingAdapter("app:imagesource") // could also be "android:src"
public static void loadGridImage(ImageView image,String imageUrl){
//we do all the magic with Picasso or any other lib here
Picasso.with(image.getContext()).load(imageUrl).into(image);
}

The Movies poster Grid

It’s time to put together all the posters on a grid like on the screenshot above and bind the images to each one of them. As we always do when it’s time to populate data to a ListView,GridView or RecyclerView we need first to create an adapter, pass the data to it and then set the adapter to back to it. The process doesn’t change but the code on how this is done can be slightly different.

  1. The first things that change is the ViewHolder of our poster view that holds a reference as we scroll throught so that the view doesn’t have be recreated again. This ViewHolder used to contain instances of all the views inside our layout and we used to to find view by id of this views at the time we were constructing it. Since we are using the data binding library, we no longer have to do the find view by id because we already have our binding class autogenerated at compile time for our use just like the first case at the begining of the post. The catch here is that since we can have a list or grid of elements with different items, specifiying the exact generated layout binding class for a certain type it’s not a really good idea and can give us a little extra work doing checks for the different type of views we might want to have on our list. The idea is that inside the ViewHolder we create an object of the base binding class ViewDataBinding, get a binding(assuming we don’t know what is going to be) from the view passed on the ViewHolder constuctor using the method bind inside the DataBindingUtil class and expose a getter method to access the binding as shown on the code below.
public class MoviesGridViewHolder extends RecyclerView.ViewHolder {

private ViewDataBinding bindingView;

public MoviesGridViewHolder(View itemView) {
super(itemView);
bindingView = DataBindingUtil.bind(itemView);
}

public ViewDataBinding getBindingView(){
return bindingView;
}

}

Inside the adapter, the implementations can be a little bit different for the list and grid views in comparison with a RecyclerView but the background idea is the same where when we create the view for the first time we create the view holder and the subsequent times we keep using the view Holder. Said this on our recycler view adapter we first create the view holder on the method onCreateViewHolder.

@Override
public MoviesGridViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view= inflater.inflate(R.layout.layout_single_movie_poster, parent, false);
MoviesGridViewHolder holder = new MoviesGridViewHolder(view);
return holder;
}

After the creation of the view holder, the next important moment is when we finally bind the data to our UI. We do this inside the onBindViewHolder method of the adapter by using the getter method inside the viewholder to get the base binding class and then call the method setVariable(int variableId, Object) that receives two parameters that are the variable id and the value of the object we want to set.

The very important thing to understand here is that the Variable Id passed is auto generated when we compile our project and added as field of the auto generated class called BR that works the same way R does with the ids on views and any other resources.

After setting all data data to the respective variables, we have to call executePendingBindings in order to sync all the views with our model and prevent the creation of two layouts when there is an attempt to fix any layout errors that might have happened on the previous animation frame. This is mainly because the data binding library doesn’t update instantly the views as the data changes instead it keep tracks of them and only do the changes on the next animation frame.

@Override
public void onBindViewHolder(MoviesGridViewHolder holder, final int position) {
final Movie movie = movies.get(position);
holder.getBindingView().setVariable(BR.movie,movie);
holder.getBindingView().setVariable(BR.clickHandler,movie.getPosterUrl());
holder.getBindingView().executePendingBindings();
}

Opening details on Thumbnail Click

With the Grid in place, the next thing we have to do it’s to open a single movie details Screen on a click of the respective thumbnail. If you have been very sceptical about the fact of not using findViewById method, chances are that you found your “aHa” moment when you started thinking about this.

But fortunately I will have to give the bad news that there is a way of handling click events without findViewById. Actually this is not something new because even before the data binding library we were able to set an onClick attribute to the view and then just create a method that passes a View as parameter and implement all the things we might want to do on the click of the specified view.

Using the data binding library we just have to create a simple class class(let’s name it a handler), write a method with the same signature as I just explained above, create a variable with the handler class inside the data tag of the layout file with the views we want to add the click behaviour and finally add the onClick attribute and on it’s value pass an expression where we call the desired method for the handler as we shown on the snippets below.

public class BindingClickHandlers {
   public void onThumbnailClick(View view){
context = view.getContext();
intent = new Intent(context, MovieDetails.class);
bundle = new Bundle();
bundle.putParcelable("movie",movie);
intent.putExtras(bundle);
context.startActivity(intent);
}
}
<?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">

<data>
<variable name="imageUrl" type="String"/>
<variable name="clickHandler" type="com.app.mymooviapp.bindingAdapters.BindingClickHandlers"/>
</data>

<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:onClick="@{clickHandler.onThumbnailClick}"
>

<ImageView
android:id="@+id/movie_thumbnail"
android:layout_width="190dp"
android:layout_height="280dp"
app:imagesource="@{imageUrl}"
/>

</LinearLayout>
</layout>

If you have been paying attention to the code snippets, you probably noticed two things on the last two.

  1. method implementation have a movie object passed to the intent that seems to come from nowhere.
  2. On the same line of thought with the first point you should have noticed that when we call the method we don’t pass any aditional parameters and the right reason is because we can’t.

The big question here is how do we get that object there?

Well, I can’t say weather it is the ultimate and best implementation but here is how I managed to work out. Since our handler will be mainly a variable we declared as shown on the last snippet of the layout code, we can create a variable with the movie inside the class and a static factory method that where we will construct the handler and set the movie object and use it inside our code like we did previously. I full implementation of the whole class would be something like this.

public class BindingClickHandlers {

private Movie movie;

private Context context;

private Intent intent;

private Bundle bundle;

public BindingClickHandlers(){}

//construct a binding class for movie thumbnail click
public static BindingClickHandlers newThumbnailClickHandler(Movie movie){
BindingClickHandlers handler = new BindingClickHandlers();
handler.setMovie(movie);
return handler;
}


public void onThumbnailClick(View view){
context = view.getContext();
intent = new Intent(context, MovieDetails.class);
bundle = new Bundle();
bundle.putParcelable("movie",movie);
intent.putExtras(bundle);
context.startActivity(intent);
}

public void setMovie(Movie movie){
this.movie = movie;
}
}

And we would just have to add some lines to our adapter and set the handler like this:

final Movie movie = movies.get(position);
final BindingClickHandlers handler = BindingClickHandlers.newThumbnailClickHandler(movie);
holder.getBinding().setVariable(BR.clickHandler,handler);

More Awesomeness — The Movie Details screen

So after the click of a single movie thumbnail the app is supposed to open a new Activity and show the movie details. As we can see on the image, compared to the first layout, this layout is a little bit more complex and apart from maybe having to include some layouts inside another’s you might end up with some big code inside your fragment just to set the data to this code and so on.

Luckily with data binding your problems can be reduced almost to nothing using some cool features such as:

Bind data to included layouts

Being as simple as possible on this, the library lets you bind a certain variable on the root layout to the include layout with the condition that the included layout have also a declaration of a variable with the same name on it’s file.

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">

<data>
<variable name="movie" type="com.app.mymooviapp"/>
</data>

<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<FrameLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<include layout="@layout/layout_details_content"
android:id="@+id/layout_details"
bind:movie="@{movie}"
/>
<!-- REST OF THE LAYoUT CODE

Import classes and use them on expressions

Looking at the header of the details screen where the movie rating is, we can see that the current movie rating appears with the total rating that it could achieve but for some rare movies with a rating of 10 out of 10 I thought it would be cool to show to remove the out of 10 since we already reached the peak. In order to do this we would have to find the two textviews by their ids and implement your logic to make it happen.

Now things get smaller because data binding let’s you import classes to use on expressions inside the layout files. This import happens inside the data tag and it is done with a simple line like this:

<import type="android.view.View"/>

One thing to notice on this is that the type should be the full name of the package where the class is and in cases that we might want to import two classes with the same name we can define an alias to distinguish one from another by adding an alias attribute and set the value as the desired name.

So after importing the class we can use it to declare as a type of one of our variables or use directly inside expressions. For the case of rating I just mentioned, by importing the View class we can use it to set the visibility of the view according to the value of the user rating. Below is a sample layout on how the import and the use of the class on an expression would look like

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

<data>
<import type="android.view.View"/>

<variable name="backdropPath" type="String"/>
<variable name="movieRating" type="int"/>

</data>

<FrameLayout
android:layout_width="match_parent"
android:layout_height="250dp"
>

<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/header"
android:scaleType="centerCrop"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/current_rating"
android:textSize="56sp"
android:layout_gravity="center"
android:textColor="@color/white"
android:text="@{movieRating}"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="56dp"
android:id="@+id/max_rating"
android:layout_marginTop="16dp"
android:layout_gravity="center"
android:textColor="@color/white"
android:text=" / 10"
android:visibility="@{movieRating == 10 ? View.GONE : View.VISIBLE}"
/>

</FrameLayout>

</layout>

This post was a short note to showcase some of the things that I consider to be really basic and interesting about the library but you might be sure that there is a lot more to explore and by the time of the final release we might have some new things.

For some more information I would advise looking at the getting started guide, the javadoc and the talk by Yigit Boyar during the Android dev summit.

Hope to hear your toughts about this topic on the comments and if you enjoyed the post hit like and recommend to some friends who might also find this interesting :)

DM