RecyclerView Adapters part 2: RecyclerView Cursor Adapter

In the Android platform, a CursorAdapter is an Adapter that exposes data from a Cursor object to a ListView widget. This second post about RecyclerView Adapters will explain on how to make a simple and reusable Cursor adapter yourself, and how to use it in your application. In a third post about RecyclerView Adapters, I’ll show a more advanced version of this CursorAdapter class.

(in contrast to an Android CursorAdapter, the Cursor used in this example, does not have to include a column named “_id”)

First we create the abstract class RecyclerViewCursorAdapter, which will hold the Cursor object and implement some of the required methods of the RecyclerView.Adapter class (like getItemCount()).

Furthermore, our adapter class will define a new method called onBindViewHolder(RecyclerView.ViewHolder, Cursor), so you don’t have to fetch the cursor object every time you need to bind data to your ViewHolder.

We also add some helper methods, like:

  • swapCursor(Cursor): to provide the Adapter with a dataset
  • getItem(int): to get the Cursor object, moved to the correct position
  • getCursor(): to get the Cursor object
import android.database.Cursor;
import android.support.v7.widget.RecyclerView;

public abstract class RecyclerViewCursorAdapter<VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH>
{
    private Cursor cursor;

    public void swapCursor(final Cursor cursor)
    {
        this.cursor = cursor;
        this.notifyDataSetChanged();
    }

    @Override
    public int getItemCount()
    {
        return this.cursor != null
                ? this.cursor.getCount()
                : 0;
    }

    public Cursor getItem(final int position)
    {
        if (this.cursor != null && !this.cursor.isClosed())
        {
            this.cursor.moveToPosition(position);
        }

        return this.cursor;
    }

    public Cursor getCursor()
    {
        return this.cursor;
    }

    @Override
    public final void onBindViewHolder(final VH holder, final int position)
    {
        final Cursor cursor = this.getItem(position);
        this.onBindViewHolder(holder, cursor);
    }

    public abstract void onBindViewHolder(final VH holder, final Cursor cursor);
}

Implementing this abstract class and feeding it with data is dead simple.

import android.content.Context;
import android.database.Cursor;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import butterknife.Bind;
import butterknife.ButterKnife;

public class SearchResultsCursorAdapter extends RecyclerViewCursorAdapter<SearchResultsCursorAdapter.SearchResultViewHolder>
    implements View.OnClickListener
{
    private final LayoutInflater layoutInflater;
    private OnItemClickListener onItemClickListener;

    public SearchResultsCursorAdapter(final Context context)
    {
        super();

        this.layoutInflater = LayoutInflater.from(context);
    }

    public void setOnItemClickListener(final OnItemClickListener onItemClickListener)
    {
        this.onItemClickListener = onItemClickListener;
    }

    @Override
    public SearchResultViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType)
    {
        final View view = this.layoutInflater.inflate(R.layout.listitem_search, parent, false);
        view.setOnClickListener(this);

        return new SearchResultViewHolder(view);
    }

    @Override
    public void onBindViewHolder(final SearchResultViewHolder holder, final Cursor cursor)
    {
        holder.bindData(cursor);
    }

     /*
     * View.OnClickListener
     */

    @Override
    public void onClick(final View view)
    {
        if (this.onItemClickListener != null)
        {
            final RecyclerView recyclerView = (RecyclerView) view.getParent();
            final int position = recyclerView.getChildLayoutPosition(view);
            if (position != RecyclerView.NO_POSITION)
            {
                final Cursor cursor = this.getItem(position);
                this.onItemClickListener.onItemClicked(cursor);
            }
        }
    }

    public static class SearchResultViewHolder extends RecyclerView.ViewHolder
    {
        @Bind(R.id.textview_name)
        TextView textViewName;

        public SearchResultViewHolder(final View itemView)
        {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }

        public void bindData(final Cursor cursor)
        {
            final String name = cursor.getString(cursor.getColumnIndex("name"));
            this.textViewName.setText(name);
        }
    }

    public interface OnItemClickListener
    {
        void onItemClicked(Cursor cursor);
    }
}

Providing the adapter with data in your Activity / Fragment:

import android.app.Activity;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;

public class SearchActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor>
{
    private static final int LOADER_SEARCH_RESULTS = 1;

    private SearchResultsCursorAdapter adapter;

    @Override
    protected void onCreate(final Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        // create adapter
        this.adapter = new SearchResultsCursorAdapter(this);

        // start loader
        this.getLoaderManager().restartLoader(LOADER_SEARCH_RESULTS, null, this);
    }

    @Override
    public Loader<Cursor> onCreateLoader(final int id, final Bundle args)
    {
        switch (id)
        {
            case LOADER_SEARCH_RESULTS:

                final Uri uri = Uri.parse("content://some_uri");
                return new CursorLoader(this, uri, null, null, null, null);
        }

        return null;
    }

    @Override
    public void onLoadFinished(final Loader<Cursor> loader, final Cursor data)
    {
        switch (loader.getId())
        {
            case LOADER_SEARCH_RESULTS:

                this.adapter.swapCursor(data);
                break;
        }
    }

    @Override
    public void onLoaderReset(final Loader<Cursor> loader)
    {
        switch (loader.getId())
        {
            case LOADER_SEARCH_RESULTS:

                this.adapter.swapCursor(null);
                break;
        }
    }
}
device-2015-09-25-135150