Skip to content

Instantly share code, notes, and snippets.

@skyfishjy
Last active December 16, 2023 08:55
Show Gist options
  • Save skyfishjy/443b7448f59be978bc59 to your computer and use it in GitHub Desktop.
Save skyfishjy/443b7448f59be978bc59 to your computer and use it in GitHub Desktop.
CursorRecyclerViewAdapter
/*
* Copyright (C) 2014 skyfish.jy@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import android.content.Context;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.support.v7.widget.RecyclerView;
/**
* Created by skyfishjy on 10/31/14.
*/
public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
private Context mContext;
private Cursor mCursor;
private boolean mDataValid;
private int mRowIdColumn;
private DataSetObserver mDataSetObserver;
public CursorRecyclerViewAdapter(Context context, Cursor cursor) {
mContext = context;
mCursor = cursor;
mDataValid = cursor != null;
mRowIdColumn = mDataValid ? mCursor.getColumnIndex("_id") : -1;
mDataSetObserver = new NotifyingDataSetObserver();
if (mCursor != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
}
public Cursor getCursor() {
return mCursor;
}
@Override
public int getItemCount() {
if (mDataValid && mCursor != null) {
return mCursor.getCount();
}
return 0;
}
@Override
public long getItemId(int position) {
if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
return mCursor.getLong(mRowIdColumn);
}
return 0;
}
@Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(true);
}
public abstract void onBindViewHolder(VH viewHolder, Cursor cursor);
@Override
public void onBindViewHolder(VH viewHolder, 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(viewHolder, mCursor);
}
/**
* 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
}
}
}
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.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by skyfishjy on 10/31/14.
*/
public class MyListCursorAdapter extends CursorRecyclerViewAdapter<MyListCursorAdapter.ViewHolder>{
public MyListCursorAdapter(Context context,Cursor cursor){
super(context,cursor);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
public ViewHolder(View view) {
super(view);
mTextView = view.findViewById(R.id.text);
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_view, parent, false);
ViewHolder vh = new ViewHolder(itemView);
return vh;
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, Cursor cursor) {
MyListItem myListItem = MyListItem.fromCursor(cursor);
viewHolder.mTextView.setText(myListItem.getName());
}
}
public class MyListItem{
private String name;
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public static MyListItem fromCursor(Cursor cursor) {
//TODO return your MyListItem from cursor.
}
}
@yaseen2591
Copy link

How can I extend this CursorRecyclerViewAdapter.java to RecyclerViews that has multiple Viewtypes??

@PRosenb
Copy link

PRosenb commented Oct 14, 2015

Instead of overwriting setHasStableIds() better call it in the constructor. As it is now, it's only called when the extending class calls setHasStableIds().
When setHasStableIds() is set to true and getItemId() returns unique ids, change animations are shown.

@luatlc
Copy link

luatlc commented Oct 15, 2015

@yaseen2591: multi viewtypes.
In CursorRecyclerViewAdapter, you add:

@OverRide
public int getItemViewType(int position) {
if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
return mCursor.getInt(mCursor.getColumnIndex(NotificationTable.COL_TYPE));
}
return 0;
}

and In NotificationCursorAdapter, you modify:
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView;

     switch (viewType) {
            case TYPE_1:

            break;
            case TYPE_2:

            break;
    }

}

@tuanchauict
Copy link

I create an exteionsion that supports section for this cursor adapter. Tested for ListView only.

https://gist.github.com/tuanchauict/c6c1eda617523de224c5

@antonshkurenko
Copy link

How do you close cursor? After setting an adapter?

@VovaStelmashchuk
Copy link

Hi for all.
Your can delete context from your adapter, because you don't use it in adapter, add RecyclerView don't want Context for work. Why you include Context in your code?

@luna-vulpo
Copy link

What should I do when I delete item form database by getWritableDatabase().delete() ? Now I have strange behavior: the onBindViewHolder is called with strange cursor.

@prateeksamaiya
Copy link

please help me with the implementation part,can you give an example program how to implement this...?

@omaakaar
Copy link

omaakaar commented Jul 9, 2016

https://omaakaar.github.io/Hello-World/
need help to take benefit for and from github.
I am newbie and want to make money online.

@conacry
Copy link

conacry commented Jul 12, 2016

Hi! Thank you very much for this class, he helped me a lot!

@pawarlalit29
Copy link

hey ,
what inside in fromCursor method
public static MyListItem fromCursor(Cursor cursor) {
//TODO return your MyListItem from cursor.
}

@MrHamidKhan
Copy link

Why holding context? We can always get it from parent.getContext()

@zishanj
Copy link

zishanj commented Nov 19, 2016

Any example on how to use it with SQLite db file in assets folder. I just want to retrieve it and display with RecyclerView using above CursorLoader.

@Emeritus-DarranKelinske

thank you @lonely1990

@AHMAD8088
Copy link

zishanj do you found your answer abov question. i want in my project display data with RecyclerView using above CursorLoader from .db file asset folder

@samuel2629
Copy link

Hey,
Where do you call on click? Cause the implementation doesn't trigger it, and when I call it in the onBind method with the cursor, the if is wrong.. Any ideas? Thanks!

@vishusrvstv
Copy link

I am facing some problem with this code. I am populating some items in recyclerview using above cursor adapter. But after scrolling, i am getting wrong cursor position on click event. Sometimes it work fine, sometime mislead the cursor by 1 or 2 positions

@anky21
Copy link

anky21 commented Jun 22, 2017

Same problem here. Will try to search a solution tomorrow. Does anyone else know a solution?

@omarhammad
Copy link

omarhammad commented Jul 31, 2017

public static MyListItem fromCursor(Cursor cursor) {
MyListItem listItem = new MyListItem();
listItem.setName(cursor.getString(cursor.getColumnIndex(STUDENT_NAME)));
return listItem;
}

@BraveEvidence
Copy link

How to sort items in ascending and descending order?

@jackz314
Copy link

I still don't get the last part, where you used the holder.mTextView.setText part to set text, MyListItem.fromCursor(cursor); to get the data, but how did you get the each data from the cursor, can you explain better? thanks

@teapack
Copy link

teapack commented Oct 29, 2017

I'm confused with this. I added my layout file for list item to the onCreateViewHolder(ViewGroup parent, int viewType) method.
Then I instantiated the adapter by calling adapter = new MyListCursorAdapter(getContext(), cursor);,
instantiated my RecyclerView: recyclerView = v.findViewById(R.id.my_recycler_view);
and then I set the adapter to the recycler view: recyclerView.setAdapter(adapter);.
What am I missing? The RecyclerView with items still doesn't show up. I added a log to the fromCursor(Cursor cursor) method in MyListItem class to see if the method is getting triggered, and no logs appear - so the fromCursor method is never called. Should I somewhere explicitly call e.g. the onBindViewHolder(ViewHolder viewHolder, Cursor cursor) method? What would be the ViewHolder - my list item?
Actually, a simple working example would be extremely helpful. I just need a guide how to "connect" the solution to my app. For comparison - when I tried using ListView and SimpleCursorAdapter to display data from Cursor, I only had to instantiate the SimpleCursorAdapter, the ListView and set the adapter to the ListView - and that was it, it was working. This solution requires me to do something more it seems, and I just can't wrap my head around it.

Edit: Nevermind, I forgot to set layout manager to the RecyclerView. I got it working now, thanks.

@mmlaif
Copy link

mmlaif commented Dec 11, 2017

work flawlessly

@8enet
Copy link

8enet commented Dec 15, 2017

android.support.v7.util.AsyncListUtil can also be simple to implement.

@AntonioVitiello
Copy link

Hello, first of all many thanks for sharing this code! Can you help me with a problem please? I have an Activity with a RecyclerView that uses an AsyncTaskLoader to load data from the database and update MyListCursorAdapter with cursor. All works fine until I delete a row from the database using ContentProvider, the adapter don't refresh and the RecyclerView that continue to show the item I deleted with a ContextMenu.
Any idea?

@AliexieievAndrew
Copy link

@skyfishjy
thanks for the example
@PRosenb
Thanks for the help. Now everything works fine ))

@catiejo
Copy link

catiejo commented Nov 12, 2018

Thank you so much for this code! The example usage was also incredibly helpful.

@NickJian
Copy link

I wonder if we can notifyChanged just for certain range, maybe we can have a lastBindIdx and compare idx within 10 item. It will make the animation work properly.

@ideep
Copy link

ideep commented Apr 16, 2019

Thank you so much for this code!

Copy link

ghost commented Feb 1, 2021

Hi! Can you help me to update my adapter with yours?

Me too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment