Android Data Binding: Adding some variability

Making View IDs Unnecessary

Have you ever looked at someone else’s layout and wondered, “Where does that value get set?” Or maybe you think that eliminating findViewById is a great first step, but there is still too much boilerplate code. Android Data Binding makes these things much easier.

Using View Holder Pattern

Let’s say that I want to show user information in your application. In the previous article, I showed to use Android Studio to generate a View Holder pattern class, using a layout like this:

user_info.xml
<?
xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="@+id/userImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/userFirstName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<TextView
android:id="@+id/userLastName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
</layout>

And then setting the data on the view:

private void setUser(User user, ViewGroup root) {
UserInfoBinding binding =
UserInfoBinding.inflate(getLayoutInflater(), root, true);
binding.userFirstName.setText(user.firstName);
binding.userLastName.setText(user.lastName);
binding.userImage.setImageBitmap(user.image);
}

While that is still much better than all that findViewById stuff, it sure is a lot of boilerplate! I can get rid of this boilerplate by using data binding expressions within the layout to make the assignments for us.

Assigning Variables

First, I add a data tag and an associated variable to use in binding expressions. Next, I use that variable in expressions for attributes that need to be set in the layout:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.myapp.model.User"
/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:src="@{user.image}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:text="@{user.firstName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
</layout>

The binding expressions in the tags are denoted with the “@{…}” format. The expressions above directly assign the User’s image, firstName, and lastName to the Views’ source and text, so you can save that boilerplate. But it still doesn’t know which User to use, so you must assign that.

private void setUser(User user, ViewGroup root) {
UserInfoBinding binding =
UserInfoBinding.inflate(getLayoutInflater(), root, true);
binding.setUser(user);
}

Pretty easy!

You can see in the layout above that the Views no longer have IDs. But what about the View Holder we were trying to generate? Because the data is bound directly to the Views, there is no need to access the individual Views! Just set the variable and it is all done.

There is also a lot less to get wrong. If, for example, you don’t have the user’s image in the landscape layout, you no longer have to check for whether the ImageView exists. The binding expressions are calculated for each layout and the ImageView isn’t in it, so no code is run to update an ImageView.

That doesn’t mean the View Holder usage is obsolete. There are still times when you’ll want to have direct access to Views and having the field will be handy. It will just be a lot less often than before.

Included Layouts

So, what about included layouts? They work, too, just as with the View Holder pattern. Let’s say, for example, that the TextViews for the user’s name are in their own included layout:

user_name.xml
<?
xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.myapp.model.User"
/>
</data>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
/>
</LinearLayout>
</layout>

Then the user variable can be assigned in the outer layout this way:

<?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="user"
type="com.example.myapp.model.User"
/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@{user.image}"
/>
<include
layout="@layout/user_name"
app:user="@{user}"
/>
</LinearLayout>
</layout>

Whenever the user is assigned (binding.setUser(…), as before), the included layout’s user variable will be set as well because of the app:user=”@{user}” variable assignment. Note, again, since direct access to views in the included layout aren’t needed (I didn’t even put IDs on the two TextViews), there isn’t an ID on the <include>.

Going Forward

I will continue to introduce Android Data Binding in small doses so that you can sprinkle it into your application without making any huge changes. Maybe you’ll get some converts in your organization instead of angry code reviewers who have no idea what you’re doing. If you want to get the full dump, feel free to read the Data Binding Guide.