RecyclerView + DataBinding

Why bother messing with views when you can just bind them with any object and not worry about anything?

In short, DataBinding allows us to eliminate a lot of boilerplate code and a lot of errors around that code.

To start we are going to take a look at a vanilla approach for displaying items in a list using RecyclerView.

What we have is a User Class that represents one user with the following attributes:

  • First name
  • Last name
  • Age
public class User {  
    private String mFirstName;
    private String mLastName;
    private int mAge;

    public User(@NonNull String firstName, @NonNull String lastName, int age) {
        mFirstName = checkNotNull(firstName);
        mLastName = checkNotNull(lastName);
        mAge = age;
    }

    public @NonNull String getFirstName() {
        return mFirstName;
    }

    public @NonNull String getLastName() {
        return mLastName;
    }

    public @NonNull String getAge() {
        return String.valueOf(mAge);
    }
}

Now what we need is an XML layout for the list row. For the purpose of this article we won’t do anything fancy.

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout  
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"

    android:layout_margin="10dp"
    android:orientation="vertical">

    <TextView
        android:id="@+id/firstName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:text="First name"/>

    <TextView
        android:id="@+id/lastName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:text="Last name"/>

    <TextView
        android:id="@+id/age"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:text="Age"/>

</LinearLayout>  

Now let’s take a look how a default implementation with no additional libraries would look like.

public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {

    private List<User> mUsers = new ArrayList<>();

    public UserAdapter(@NonNull List<User> users) {
        mUsers.addAll(users);
    }

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

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        User user = mUsers.get(position);
        holder.mFirstName.setText(user.getFirstName());
        holder.mLastName.setText(user.getLastName());
        holder.mAge.setText(String.valueOf(user.getAge()));
    }

    @Override
    public int getItemCount() {
        return mUsers.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mFirstName, mLastName, mAge;
        public ViewHolder(View itemView) {
            super(itemView);
            mFirstName = (TextView) itemView.findViewById(R.id.firstName);
            mLastName = (TextView) itemView.findViewById(R.id.lastName);
            mAge = (TextView) itemView.findViewById(R.id.age);
        }
    }
}

We could of course use some kind of binding library - like ButterKnife - to get rid of the findViewById calls, but that’s not the point here so let’s leave it like that.

Usually the biggest issue is handling the views in the code. We need to hold them in variables and set the appropriate values on them by hand in the onBindViewHolder method.

Here comes DataBinding!

So basically what DataBinding allows us to do is get rid of all the code associated with views like findViewById() calls, view references, setting listeners on views etc.

Most of the binding is done in the XML layout. We are going to need to update the Adapter code as well.

Let’s jump right into it.

<?xml version="1.0" encoding="utf-8"?>

<layout  
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="user"
            type="pl.rst.example.ui.model.User"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/firstName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user.firstName}"/>

        <TextView
            android:id="@+id/lastName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user.lastName}"/>

        <TextView
            android:id="@+id/age"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user.age}"/>

    </LinearLayout>
</layout>  

As you can see, the list row layout changed a lot. It is wrapped in a *layout *tag and has a *data *section. Also, the value is set directly in the XML file.

By using the @{} statement we can access the getters of the User Class directly from here. The possibilities are very broad, but in this example we will be just setting the value on the view.

What we also need to do is update the Adapter class to take advantage of what we have just done

public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {

    private List<User> mUsers = new ArrayList<>();

    public UserAdapter(@NonNull List<User> users) {
        mUsers.addAll(users);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        RowUserBinding binding = DataBindingUtil.inflate(inflater, R.layout.row_user, parent, false);
        return new ViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        User user = mUsers.get(position);
        holder.bind(user);
    }

    @Override
    public int getItemCount() {
        return mUsers.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        private RowUserBinding mBinding;
        public ViewHolder(RowUserBinding binding) {
            super(binding.getRoot());
            mBinding = binding;
        }

        public void bind(@NonNull User user) {
            mBinding.setUser(user);
            mBinding.executePendingBindings();
        }
    }
}

Here is what we’ve done:

  1. Updated the ViewHolder class to use binding, removed view variables from it.

  2. Updated onCreateViewHolder so that it creates a Binding instance.
    The important part here is that we have a new class -RowUserBinding. It was automatically generated when we updated the row layout.

  3. Updated onBindViewHolder

As you can see, there is a lot less code, no view finding, no keeping views in variables, no view code at all, just inflating. This is objectively a big upside of using DataBinding that can save a lot of time and headaches.

As stated before, the possibilities are vast. We can change view attributes, we can use some basic logic in the XML file and if we need something more complex we can use BindingAdapters. But that’s a topic for a different article.