Skip to content

Instantly share code, notes, and snippets.

@tcw165
Last active April 9, 2017 06:20
Show Gist options
  • Save tcw165/14b548ae7d2f6b4d89eeb62fbd68d540 to your computer and use it in GitHub Desktop.
Save tcw165/14b548ae7d2f6b4d89eeb62fbd68d540 to your computer and use it in GitHub Desktop.
public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
final private WeakReference<Context> mContext;
final private WeakReference<LayoutInflater> mInflater;
private RecyclerView mRecyclerView;
private Cursor mCursor;
// State.
private boolean mDataValid;
private int mRowIdColumn;
public CursorRecyclerViewAdapter(Context context) {
mContext = new WeakReference<>(context);
mInflater = new WeakReference<>(LayoutInflater.from(context));
mDataValid = false;
mRowIdColumn = -1;
// Enable stable-ID so that it is able to find ViewHolder by item ID.
setHasStableIds(true);
}
@Override
public int getItemCount() {
if (mDataValid && mCursor != null && !mCursor.isClosed()) {
return mCursor.getCount();
} else {
return 0;
}
}
@Override
public long getItemId(int position) {
if (mDataValid &&
mCursor != null && !mCursor.isClosed() &&
mCursor.moveToPosition(position)) {
return mCursor.getLong(mRowIdColumn);
} else {
return -1;
}
}
@Override
public void onBindViewHolder(VH viewHolder,
int position) {
this.onBindViewHolder(viewHolder, position, null);
}
@Override
public void onBindViewHolder(VH viewHolder,
int position,
List<Object> payloads) {
try {
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);
}
if (BuildConfig.DEBUG) {
Log.d("@", "onBindViewHolder(" + position +
"), context=" + getContext() +
", running on " + Looper.myLooper());
}
onBindViewHolder(viewHolder, mCursor, payloads);
} catch (Throwable exception) {
if (BuildConfig.DEBUG) {
Log.d("@", "onBindViewHolder(" + position +
"), error=" + exception.getMessage());
}
}
}
/**
* Called by RecyclerView to display the data at the specified position.
*
* @param viewHolder The ViewHolder which should be updated to represent
* the contents of the item at the given position in
* the data set.
* @param cursor The cursor of the item within the adapter's data set.
* @param payloads A list of merged payloads (could be null). Can be
* empty list if requires full update.
*/
public abstract void onBindViewHolder(final VH viewHolder,
final Cursor cursor,
final List<Object> payloads);
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
}
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
super.onDetachedFromRecyclerView(recyclerView);
mRecyclerView = null;
}
@SuppressWarnings("unused")
final public Context getContext() {
return mContext.get();
}
@SuppressWarnings("unused")
final public LayoutInflater getInflater() {
return mInflater.get();
}
@SuppressWarnings("unused")
final public Cursor getCursor() {
return mCursor;
}
@SuppressWarnings("unused")
final public RecyclerView getRecyclerView() {
return mRecyclerView;
}
/**
* Return the ViewHolder for the item with the given id. The RecyclerView
* must use an Adapter with
* {@link RecyclerView.Adapter#setHasStableIds(boolean) stableIds}
* to return a non-null value.
* <p>
* This method checks only the children of RecyclerView. If the item with
* the given <code>id</code> is not laid out, it <em>will not</em> create
* a new one.
* <p>
* When the ItemAnimator is running a change animation, there might be 2
* ViewHolders with the same id. In this case, the updated ViewHolder will
* be returned.
*
* @param id The id for the requested item
*
* @return The ViewHolder with the given <code>id</code> or null if there
* is no such item
*/
@SuppressWarnings("unused")
final public RecyclerView.ViewHolder findViewHolderForItemId(final long id) {
if (mRecyclerView == null) {
throw new IllegalStateException(
"No RecyclerView is observing this adapter.");
}
return mRecyclerView.findViewHolderForItemId(id);
}
/**
* Return the ViewHolder for the item in the given position of the data set.
* Unlike {@link RecyclerView#findViewHolderForLayoutPosition(int)} this
* method takes into account any pending adapter changes that may not be
* reflected to the layout yet. On the other hand, if
* {@link RecyclerView.Adapter#notifyDataSetChanged()}
* has been called but the new layout has not been calculated yet, this
* method will return <code>null</code> since the new positions of views are
* unknown until the layout is calculated.
* <p>
* This method checks only the children of RecyclerView. If the item at the given
* <code>position</code> is not laid out, it <em>will not</em> create a new one.
* <p>
* When the ItemAnimator is running a change animation, there might be 2 ViewHolders
* representing the same Item. In this case, the updated ViewHolder will be returned.
*
* @param position The position of the item in the data set of the adapter
* @return The ViewHolder at <code>position</code> or null if there is no such item
*/
@SuppressWarnings("unused")
final public RecyclerView.ViewHolder findViewHolderForAdapterPosition(final int position) {
if (mRecyclerView == null) {
throw new IllegalStateException(
"No RecyclerView is observing this adapter.");
}
return mRecyclerView.findViewHolderForAdapterPosition(position);
}
/**
* Change the underlying cursor to a new cursor. If there is an existing
* cursor it will be closed.
*/
@SuppressWarnings("unused")
public void setData(Cursor cursor) {
Cursor old = swapCursor(cursor);
if (old != null && !old.isClosed()) {
old.close();
}
}
///////////////////////////////////////////////////////////////////////////
// Protected / Private Methods ////////////////////////////////////////////
/**
* Swap in a new Cursor, returning the old Cursor. Unlike
* {@link #setData(Cursor)}, the returned old Cursor is <em>not</em>
* closed.
*/
private Cursor swapCursor(Cursor newCursor) {
if (newCursor == mCursor) {
return null;
}
final Cursor oldCursor = mCursor;
if (oldCursor != null && mDataSetObserver != null) {
oldCursor.unregisterDataSetObserver(mDataSetObserver);
oldCursor.close();
}
mCursor = newCursor;
if (mCursor != null) {
if (mDataSetObserver != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
mRowIdColumn = newCursor.getColumnIndexOrThrow(BaseColumns._ID);
mDataValid = true;
} else {
mRowIdColumn = -1;
mDataValid = false;
//There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
}
notifyDataSetChanged();
return oldCursor;
}
// FIXME: Seems not work.
final private DataSetObserver mDataSetObserver = new 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
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment