Pages

Tuesday, 5 January 2016

Data binding in Android RecyclerView

It is recommended that you go through the post Data binding in Android ListView before reading this one.

Build Setup

Enable data binding in the build.gradle of your app, code snippet is shown below,
dataBinding {
    enabled = true
}

For more information on how to set-up your project with data binding please visit http://developer.android.com/tools/data-binding/guide.html

In this post we will have an application with a list of an ImageView and a TextView in each row. Very similar to that of Data binding in Android ListView

Model

Our model class used for individual rows of the list will have an integer member to store the image resource and a string member.
package com.example.databindingrecyclerview;

public class AndroidInfo {
    public int iconResource; /* Resource id for the image */
    public String name;      /* Name */

    public AndroidInfo(int r, String n) {
        iconResource = r;
        name = n;
    }
}

For the entire list we will have another class which stores a list of the above class.
package com.example.databindingrecyclerview;

import android.databinding.ObservableArrayList;
import android.view.View;

public class AndroidInfoList {
    public ObservableArrayList<AndroidInfo> list = new ObservableArrayList<>();
    private int mTotalCount;

    public AndroidInfoList() {
        for (mTotalCount =1; mTotalCount <11; ++mTotalCount) {
            add(new AndroidInfo(android.R.drawable.sym_def_app_icon, "icon_" + (mTotalCount)));
        }
    }

    // Called on add button click
    public void add(View v) {
        list.add(new AndroidInfo(android.R.drawable.sym_def_app_icon, "icon_" + mTotalCount++));
    }

    // Called on remove button click
    public void remove(View v) {
        if (!list.isEmpty()) {
            list.remove(0);
        }
    }

    private void add(AndroidInfo info) {
        list.add(info);
    }
}

Layout

We will have two layout files,  one for the list item and the other the main layout file.
Layout file for the list items is shown below,
<?xml version="1.0" encoding="utf-8"?>

<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto">
    <data>
        <variable name="info" type="com.example.databindingrecyclerview.AndroidInfo"/>
    </data>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:weightSum="2">
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            bind:imageRes="@{info.iconResource}"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@{info.name}"/>
    </LinearLayout>

</layout>

Attribute bind:imageRes will be used to set the image for ImageView from resource id.
Implementation of which is shown below,
    @BindingAdapter("bind:imageRes")
    public  static void bindImage(ImageView view, int r) {
        view.setImageResource(r);
    }

Main Layout file

Our main layout file is shown below,
<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto">
    <data>
        <variable name="heading" type="com.example.databindingrecyclerview.ListHeading"/>
        <variable name="infos" type="com.example.databindingrecyclerview.AndroidInfoList"/>
    </data>

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

        <LinearLayout
            android:id="@+id/opLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <Button
                android:id="@+id/addItem"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:onClick="@{infos.add}"
                android:text="Add"/>
            <Button
                android:id="@+id/removeItem"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:onClick="@{infos.remove}"
                android:text="Remove"/>
        </LinearLayout>

        <TextView
            android:id="@+id/listHeading"
            android:layout_margin="10dp"
            android:gravity="center"
            android:text="@{heading.title}"
            android:layout_width="match_parent"
            android:layout_height="62dp"/>

        <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical"
            bind:items="@{infos.list}"/>

    </LinearLayout>

</layout>

Implementation for bind:items is shown below,
    
@BindingAdapter("bind:items")
public  static void bindList(RecyclerView view, ObservableArrayList<AndroidInfo> list) {
    LinearLayoutManager layoutManager = new LinearLayoutManager(view.getContext());
    view.setLayoutManager(layoutManager);
    view.setAdapter(new AndroidInfoAdapter(list));
}

Adapter implementation

package com.example.databindingrecyclerview;

import android.databinding.DataBindingUtil;
import android.databinding.ObservableArrayList;
import android.databinding.ViewDataBinding;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.support.v7.widget.RecyclerView;

import com.example.databindingrecyclerview.databinding.ListItemBinding;

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

    public class ViewHolder extends RecyclerView.ViewHolder {
        public ListItemBinding binder;

        public ViewHolder(View v) {
            super(v);
            binder = DataBindingUtil.bind(v);
        }
    }

    private ObservableArrayList<AndroidInfo> list;

    public AndroidInfoAdapter(ObservableArrayList<AndroidInfo> l) {
        list = l;
    }

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

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        final AndroidInfo info = list.get(position);
        holder.binder.setInfo(info);
        holder.binder.executePendingBindings();
    }

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

}

Binding the data

Binding the data to RecyclerView will be done in the onCreate of the activity, which is listed below,
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    MainBinding binding = DataBindingUtil.setContentView(this, R.layout.main);

    // Set the infos heading
    binding.setHeading(new ListHeading("List Heading"));

    // Set list items
    AndroidInfoList infos = new AndroidInfoList();
    binding.setInfos(infos);
}

4 comments:

  1. Hi, congratulations for such a nice series of posts. We are implementing our RecyclerView with Data binding this way but we are facing a tiny design problem. When we create a new adapter (as you did with new AndroidInfoAdapter(list)), we want to pass a second parameter to the adapter. This second parameter is any class implementing an interface we have defined, in our case it's the activity itself. So we need to pass a reference to the activity to AndroidInfoList class. We do this using a constructor parameter. The problem begins when we try to pass that reference to the AndroidInfoAdapter instantiation as new AndroidInfoAdapter(list) it's called in a static methd (annotated with @BindingAdapter). Any idea how we can solve this?

    ReplyDelete
  2. As a business head, your prime objective would be to promote your products or service far and wide. Using online scheduling software is another effective to promote your business as your company would be listed in a director and it would appear in the search engine results page. driver toolkit key

    ReplyDelete
  3. Great post. It helped me to understand some missing points. Thanks...

    ReplyDelete