package com.cncoderx.recyclerviewhelper.adapter; import android.database.ContentObserver; import android.database.Cursor; import android.database.DataSetObserver; import android.os.Handler; import androidx.annotation.WorkerThread; import android.util.Log; import android.widget.Filter; import android.widget.FilterQueryProvider; import android.widget.Filterable; /** * @author cncoderx */ public abstract class CursorAdapter extends BaseAdapter implements Filterable, CursorFilter.CursorFilterClient { protected boolean mDataValid; protected boolean mAutoRequery; protected Cursor mCursor; protected int mRowIDColumn; protected ChangeObserver mChangeObserver; protected DataSetObserver mDataSetObserver; protected CursorFilter mCursorFilter; protected FilterQueryProvider mFilterQueryProvider; @Deprecated public static final int FLAG_AUTO_REQUERY = 0x01; public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02; @Deprecated public CursorAdapter(Cursor c) { init(c, FLAG_AUTO_REQUERY); } public CursorAdapter(Cursor c, boolean autoRequery) { init(c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER); } public CursorAdapter(Cursor c, int flags) { init(c, flags); } @Deprecated protected void init(Cursor c, boolean autoRequery) { init(c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER); } void init(Cursor c, int flags) { if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) { flags |= FLAG_REGISTER_CONTENT_OBSERVER; mAutoRequery = true; } else { mAutoRequery = false; } boolean cursorPresent = c != null; mCursor = c; mDataValid = cursorPresent; mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1; if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) { mChangeObserver = new ChangeObserver(); mDataSetObserver = new MyDataSetObserver(); } else { mChangeObserver = null; mDataSetObserver = null; } if (cursorPresent) { if (mChangeObserver != null) c.registerContentObserver(mChangeObserver); if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver); } setHasStableIds(true); } public Cursor getCursor() { return mCursor; } @Override public int getItemCount() { if (mDataValid && mCursor != null) { return mCursor.getCount(); } else { return 0; } } public Object getItem(int position) { if (mDataValid && mCursor != null) { mCursor.moveToPosition(position); return mCursor; } else { return null; } } @Override public long getItemId(int position) { if (mDataValid && mCursor != null) { if (mCursor.moveToPosition(position)) { return mCursor.getLong(mRowIDColumn); } else { return 0; } } else { return 0; } } @Override public final void setHasStableIds(boolean hasStableIds) { super.setHasStableIds(true); } @Override public void onBindViewHolder(BaseViewHolder 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); } public abstract void onBindViewHolder(BaseViewHolder holder, Cursor cursor); @Override 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 (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver); if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver); } mCursor = newCursor; if (newCursor != null) { if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver); if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver); mRowIDColumn = newCursor.getColumnIndexOrThrow("_id"); mDataValid = true; // notify the observers about the new cursor notifyDataSetChanged(); } else { mRowIDColumn = -1; mDataValid = false; // notify the observers about the lack of a data set notifyDataSetChanged(); } return oldCursor; } public CharSequence convertToString(Cursor cursor) { return cursor == null ? "" : cursor.toString(); } @WorkerThread public Cursor runQueryOnBackgroundThread(CharSequence constraint) { if (mFilterQueryProvider != null) { return mFilterQueryProvider.runQuery(constraint); } return mCursor; } public Filter getFilter() { if (mCursorFilter == null) { mCursorFilter = new CursorFilter(this); } return mCursorFilter; } public FilterQueryProvider getFilterQueryProvider() { return mFilterQueryProvider; } public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) { mFilterQueryProvider = filterQueryProvider; } protected void onContentChanged() { if (mAutoRequery && mCursor != null && !mCursor.isClosed()) { if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update"); mDataValid = mCursor.requery(); } } private class ChangeObserver extends ContentObserver { public ChangeObserver() { super(new Handler()); } @Override public boolean deliverSelfNotifications() { return true; } @Override public void onChange(boolean selfChange) { onContentChanged(); } } private class MyDataSetObserver extends DataSetObserver { @Override public void onChanged() { mDataValid = true; notifyDataSetChanged(); } @Override public void onInvalidated() { mDataValid = false; notifyDataSetChanged(); } } }