4
\$\begingroup\$

I am trying to get a recycler viewer with a cursor adapter. I seem to miss important points, so my view gets some of the data repeated instead of all the data at once. Can anyone who knows about this issue review this code and help me find the problem?

import android.content.Context;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.net.Uri;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.squareup.picasso.Picasso;

import java.util.Random;

import amistore.nativeapps.com.amistore.data.ProductContract;


public class RVAdapter extends RecyclerView.Adapter<RVAdapter.ProductViewHolder> {

private final String LOG_TAG = RVAdapter.class.getName();




// PATCH: Because RecyclerView.Adapter in its current form doesn't natively support
// cursors, we "wrap" a CursorAdapter that will do all teh job
// for us
CursorAdapter mCursorAdapter;


private Cursor mCursor;

protected boolean mDataValid;

private DataSetObserver mDataSetObserver;

private Context mContext;
private ProductViewHolder pvh;
private int mRowIdColumn;
//List<Product> products;



public RVAdapter(Context context, Cursor cursor, String locationSetting) {

    mContext = context;


    // Sort order:  Ascending, by date.
    String sortOrder = ProductContract.ProductEntry.COLUMN_DATE + " ASC";
    Uri productForLocationUri = ProductContract.ProductEntry.buildProductLocationWithStartDate(
            locationSetting, System.currentTimeMillis());

    // Students: Uncomment the next lines to display what what you stored in the bulkInsert
    mCursor = mContext.getContentResolver().query(productForLocationUri,
            null, null, null, sortOrder);


    Log.d(LOG_TAG, "Const. reached ");
    Log.d(LOG_TAG, " mCursor  " + Integer.valueOf(mCursor.getCount()).toString());


    mCursorAdapter = new CursorAdapter(mContext, mCursor, 0) {

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            // Inflate the view

            Log.d("CHEKC", "newView reached  ");

            //Choose the layout type
            //  int viewType = getItemViewType(cursor.getPosition());
            // int layoutId = -1;

            View view =  LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
            //  View view = LayoutInflater.from(context).inflate(layoutId, parent, false);

            pvh = new ProductViewHolder(view);
            view.setTag(pvh);

            return view;


        }

        @Override
        public void bindView(View view, Context context, Cursor cursor) {

            Log.d("CHEKC", "BindView reached");
            // Binding operations
            ProductViewHolder viewHolder = (ProductViewHolder) view.getTag();

            //int viewType = getItemViewType(cursor.getPosition());

            // Read ImagePath from cursor
            String Image_Path = cursor.getString(ShopFragment.COL_PRODUCT_IMAGE);
            Log.d(LOG_TAG, "path: " + Image_Path );

            String Price = cursor.getString(ShopFragment.COL_PRODUCT_PRICE);


            viewHolder.productPrice.setText("US $" + Price);

            int i = new Random(50).nextInt();

            int height = new Random(i).nextInt(50) + 500;


            //Download image using picasso library
            Picasso.with(mContext).load(Image_Path)
                    .resize(500, height)
                    .error(R.drawable.placeholder)
                    .placeholder(R.drawable.placeholder)
                    .into(viewHolder.productPhoto);
        }
    };


    mDataSetObserver = new NotifyingDataSetObserver();
    if (mCursor != null) {
        mCursor.registerDataSetObserver(mDataSetObserver);
    }

    boolean cursorPresent = (mCursor != null);
    mDataValid = cursorPresent;
    mRowIdColumn = cursorPresent ? mCursor.getColumnIndexOrThrow("_id") : -1;
    setHasStableIds(true);
}

/**
 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
 * closed.
 */
public void changeCursor(Cursor cursor) {
    Cursor old = swapCursor(cursor);
    if (old != null) {
        old.close();
    }
}

/**
 * Swap in a new Cursor, returning the old Cursor.  Unlike
 * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
 * closed.
 */
public Cursor swapCursor(Cursor newCursor) {
    if (newCursor == mCursor) {
        return null;
    }
    final Cursor oldCursor = mCursor;
    if (oldCursor != null && mDataSetObserver != null) {
        oldCursor.unregisterDataSetObserver(mDataSetObserver);
    }
    mCursor = newCursor;
    if (mCursor != null) {
        if (mDataSetObserver != null) {
            mCursor.registerDataSetObserver(mDataSetObserver);
        }
        mRowIdColumn = newCursor.getColumnIndexOrThrow("_id");
        mDataValid = true;
        notifyDataSetChanged();
    } else {
        mRowIdColumn = -1;
        mDataValid = false;
        notifyDataSetChanged();
        //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
    }
    return oldCursor;
}

private class NotifyingDataSetObserver extends DataSetObserver {
    @Override
    public void onChanged() {
        super.onChanged();
        mDataValid = true;
        notifyDataSetChanged();
    }

    @Override
    public void onInvalidated() {
        super.onInvalidated();
        mDataValid = false;
        notifyDataSetChanged();
        //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
    }
}




public static class ProductViewHolder extends RecyclerView.ViewHolder {

    CardView cv;
    TextView productPrice;
    ImageView productPhoto;

    ProductViewHolder(View itemView) {
        super(itemView);
        cv = (CardView) itemView.findViewById(R.id.cv);
        productPrice = (TextView) itemView.findViewById(R.id.product_price);
        productPhoto = (ImageView) itemView.findViewById(R.id.product_photo);
    }
}



@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
}

@Override
public ProductViewHolder onCreateViewHolder(ViewGroup parent, int i) {
   // View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
    View v = mCursorAdapter.newView(mContext, mCursorAdapter.getCursor(), parent);
    pvh = new ProductViewHolder(v);
    return pvh;
}


@Override
public void onBindViewHolder(ProductViewHolder holder, int position) {
    // Passing the binding operation to cursor loader
    mCursorAdapter.getCursor().moveToPosition(position); //EDITED: added this line as suggested in the comments below, thanks :)
    mCursorAdapter.bindView(holder.itemView, mContext, mCursorAdapter.getCursor());

}

    @Override
    public int getItemCount() {

        Log.d("ITEM COUNT ", Integer.valueOf(mCursorAdapter.getCount()).toString());
        return mCursorAdapter.getCount();


    }


}
\$\endgroup\$
1

1 Answer 1

7
\$\begingroup\$

First of all CursorAdapter is not designed for use with a RecyclerView. You are trying to hack something in there that would never work right. You can't just call it's methods and expect it to function correctly. See the source.

So first things first. We want to use a cursor.. lets separate off this responsibility and create the RecyclerViewCursorAdapter. It's what it says, a CursorAdapter, for a RecyclerView. The details of this are pretty much the same how CursorAdapter works. See its source to see what is the same and what is not.

Next, we now have your original class RVAdapter to implement the abstract RecyclerViewCursorAdapter which requires us to implement onCreateViewHolder and our new onBindViewHolder which gives us a Cursor parameter to bind against. The details of these views are the same as your original just tidied up a little.

RVAdapter.java

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

import com.squareup.picasso.Picasso;

import java.util.Random;


public class RVAdapter extends RecyclerViewCursorAdapter<RVAdapter.ProductViewHolder>
{
    private static final String TAG = RVAdapter.class.getSimpleName();
    private final Context mContext;
    private final Random mRandom;


    public RVAdapter(Context context, String locationSetting)
    {
        super(null);
        mContext = context;
        mRandom  = new Random(System.currentTimeMillis());

        // Sort order:  Ascending, by date.
        String sortOrder = ProductContract.ProductEntry.COLUMN_DATE + " ASC";
        Uri productForLocationUri = ProductContract.ProductEntry
            .buildProductLocationWithStartDate(locationSetting, System.currentTimeMillis());

        // Students: Uncomment the next lines to display what what you stored in the bulkInsert
        Cursor cursor = mContext.getContentResolver()
            .query(productForLocationUri, null, null, null, sortOrder);

        swapCursor(cursor);
    }

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

    @Override
    protected void onBindViewHolder(ProductViewHolder holder, Cursor cursor)
    {
        String imagePath = cursor.getString(ShopFragment.COL_PRODUCT_IMAGE);
        String price = cursor.getString(ShopFragment.COL_PRODUCT_PRICE);

        holder.productPrice.setText("US $" + price);

        int height = mRandom.nextInt(50) + 500;

        //Download image using picasso library
        Picasso.with(mContext)
            .load(imagePath)
            .resize(500, height)
            .error(R.drawable.placeholder)
            .placeholder(R.drawable.placeholder)
            .into(holder.productPhoto);
    }


    public static class ProductViewHolder extends RecyclerView.ViewHolder
    {
        CardView cv;
        TextView productPrice;
        ImageView productPhoto;

        ProductViewHolder(View itemView)
        {
            super(itemView);
            cv = (CardView) itemView.findViewById(R.id.cv);
            productPrice = (TextView) itemView.findViewById(R.id.product_price);
            productPhoto = (ImageView) itemView.findViewById(R.id.product_photo);
        }
    }
}

RecyclerViewCursorAdapter.java

import android.database.Cursor;
import android.database.DataSetObserver;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;


/**
 * RecyclerView CursorAdapter
 * <p>
 * Created by Simon on 28/02/2016.
 */
public abstract class RecyclerViewCursorAdapter<VH extends RecyclerView.ViewHolder> extends
    RecyclerView.Adapter<VH>
{
    private Cursor mCursor;
    private boolean mDataValid;
    private int mRowIDColumn;


    public RecyclerViewCursorAdapter(Cursor cursor)
    {
        setHasStableIds(true);
        swapCursor(cursor);
    }

    public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);

    protected abstract void onBindViewHolder(VH holder, Cursor cursor);

    @Override
    public void onBindViewHolder(VH holder, int position)
    {
        if(!mDataValid){
            throw new IllegalStateException("this should only be called when the cursor is valid");
        }
        if(!mCursor.moveToPosition(position)){
            throw new IllegalStateException("couldn't move cursor to position " + position);
        }
        onBindViewHolder(holder, mCursor);
    }

    @Override
    public long getItemId(int position)
    {
        if(mDataValid && mCursor != null && mCursor.moveToPosition(position)){
            return mCursor.getLong(mRowIDColumn);
        }
        return RecyclerView.NO_ID;
    }

    @Override
    public int getItemCount()
    {
        if(mDataValid && mCursor != null){
            return mCursor.getCount();
        }
        else{
            return 0;
        }
    }

    protected Cursor getCursor()
    {
        return mCursor;
    }

    public void changeCursor(Cursor cursor)
    {
        Cursor old = swapCursor(cursor);
        if(old != null){
            old.close();
        }
    }

    public Cursor swapCursor(Cursor newCursor)
    {
        if(newCursor == mCursor){
            return null;
        }
        Cursor oldCursor = mCursor;
        if(oldCursor != null){
            if(mDataSetObserver != null){
                oldCursor.unregisterDataSetObserver(mDataSetObserver);
            }
        }
        mCursor = newCursor;
        if(newCursor != null){
            if(mDataSetObserver != null){
                newCursor.registerDataSetObserver(mDataSetObserver);
            }
            mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
            mDataValid = true;
            notifyDataSetChanged();
        }
        else{
            mRowIDColumn = -1;
            mDataValid = false;
            notifyDataSetChanged();
        }
        return oldCursor;
    }


    private DataSetObserver mDataSetObserver = new DataSetObserver()
    {
        @Override
        public void onChanged()
        {
            mDataValid = true;
            notifyDataSetChanged();
        }

        @Override
        public void onInvalidated()
        {
            mDataValid = false;
            notifyDataSetChanged();
        }
    };
}
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Can you please add an explanation of what you improved and how? \$\endgroup\$
    – janos
    Feb 28, 2016 at 11:54
  • 1
    \$\begingroup\$ Updated with some more comments to better explain what was done and why. \$\endgroup\$
    – Simon
    Feb 28, 2016 at 14:43
  • \$\begingroup\$ Since every change causes a notifyDataSetChanged() call, you eliminate all the nifty built-in UI methods of RecyclerViews. No smooth scrolling when an item is added or removed. And worse: it's horribly inefficient (imagining how long it would take if your RecyclerView displayed phonebooks!). Essentially you're just degrading the RecyclerView to a ListView, but you're doing it the hard way as ListView DOES have a CursorAdapter. \$\endgroup\$
    – SMBiggs
    Jun 2, 2018 at 5:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.