ListView filtering with CursorLoader AND FilterQueryProvider?
android, android-cursorloader, filter, list, listview
Solution
A little bit late but I have faced exact same problem and comments were not well explanatory. For those who faces the problem here is the solution:
"Stop using FilterQueryProvider. CursorLoader is also a replacement for that: restart it with your filter and you'll be fine." (source)
Which means;
instead of doing this:
@Override
public Cursor runQuery(CharSequence listId) {
return mDb.getContacts(listId);
}// DO NOT set direct database query, has no observation at all.
or
@Override
public Cursor runQuery(CharSequence listId) {
return getContentManager.query(....,new String[]{listId}...
}// DO NOT return content manager result, forgets observers.
or
@Override
public Cursor runQuery(CharSequence listId) {
Bundle bundle = new Bundle();
bundle.putCharSequence("selected", listId);
getLoaderManager().restartLoader(CURSOR_LOADER_CONTACTS, bundle, MainActivity.this);
return null;
}// DO NOT restart the cursor loader with new info, screws observers.
with combination of `mContactAdapter.getFilter().filter(id+"");` (while applying filter)
do this:
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case CURSOR_LOADER_CONTACTS:
long mListId = 0;
if (args != null) {
mListId = args.getLong(CONTACT_FILTER);
}
return new CursorLoader(this, ContentProvider.CONTACT_DISPLAY_URI, null, ContactTable.COLUMN_GROUP_ID+"=?", new String[]{id+""}, null);
} //Apply filter here with new info comes from 'restartLoader'
return null;
}
with combination of `getLoaderManager().restartLoader(MainActivity.CURSOR_LOADER_CONTACTS, bundle, MainActivity.this);`
assuming `MainActivity.this` implements `LoaderManager.LoaderCallbacks<Cursor>`
Problem
In our Project we deal a lot with Lists and used it in the past with the following "pattern": ListView is in a Fragment, gets initialized in onActivityCreated where we first start CursorLoaders and then in onFinish swapCusor to the ListAdapter. We then implemented a search functionality using the filterQueryProvider which just returns a cursor with contentResolver.query(...). If i have done some orientation changes while something in the list was selected i got the following error in many cases (not regularly): ``` android.database.StaleDataException: Attempted to access a cursor after it has been closed. 12-05 10:36:59.531: E/ACRA(12079): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1967) 12-05 10:36:59.531: E/ACRA(12079): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1992) 12-05 10:36:59.531: E/ACRA(12079): at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3378) 12-05 10:36:59.531: E/ACRA(12079): at android.app.ActivityThread.access$700(ActivityThread.java:127) 12-05 10:36:59.531: E/ACRA(12079): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1162) 12-05 10:36:59.531: E/ACRA(12079): at android.os.Handler.dispatchMessage(Handler.java:99) 12-05 10:36:59.531: E/ACRA(12079): at android.os.Looper.loop(Looper.java:137) 12-05 10:36:59.531: E/ACRA(12079): at android.app.ActivityThread.main(ActivityThread.java:4448) 12-05 10:36:59.531: E/ACRA(12079): at java.lang.reflect.Method.invokeNative(Native Method) 12-05 10:36:59.531: E/ACRA(12079): at java.lang.reflect.Method.invoke(Method.java:511) 12-05 10:36:59.531: E/ACRA(12079): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:823) 12-05 10:36:59.531: E/ACRA(12079): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:590) 12-05 10:36:59.531: E/ACRA(12079): at dalvik.system.NativeStart.main(Native Method) 12-05 10:36:59.531: E/ACRA(12079): Caused by: android.database.StaleDataException: Attempted to access a cursor after it has been closed. 12-05 10:36:59.531: E/ACRA(12079): at android.database.BulkCursorToCursorAdaptor.throwIfCursorIsClosed(BulkCursorToCursorAdaptor.java:75) 12-05 10:36:59.531: E/ACRA(12079): at android.database.BulkCursorToCursorAdaptor.getColumnNames(BulkCursorToCursorAdaptor.java:170) 12-05 10:36:59.531: E/ACRA(12079): at android.database.AbstractCursor.getColumnIndex(AbstractCursor.java:248) 12-05 10:36:59.531: E/ACRA(12079): at android.database.AbstractCursor.getColumnIndexOrThrow(AbstractCursor.java:266) 12-05 10:36:59.531: E/ACRA(12079): at android.database.CursorWrapper.getColumnIndexOrThrow(CursorWrapper.java:78) 12-05 10:36:59.531: E/ACRA(12079): at android.support.v4.widget.CursorAdapter.swapCursor(CursorAdapter.java:344) 12-05 10:36:59.531: E/ACRA(12079): at ``` this is case when using swapCursor in onLoadFinished after the orientation Change. I now reimplemented the filter functionality using restartLoader with arguments and using the Contacts.CONTENT_FILTER_URI with appended path which is the constraint and then swapping this cursor in onLoadFinished, so i removed the filterQueryProvider, which looks to work fine. The question is: Is there a possibility (or good practise) to use CursorLoader initially AND the filterQueryProvider? or do i have to decide? because the same result i reach when just using the filterQueryProvider and just do the filtering with constraint null which just loads my required contactlist and does the filtering afterwards. Any recommendations? I did not found mathinc information on this via google ;) this is my current LoaderCallbacks implementation btw: ``` private LoaderManager.LoaderCallbacks<Cursor> phoneBookContactsLoaderCallback = new LoaderManager.LoaderCallbacks<Cursor>() { @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { // if(constraint != null && constraint.length() > 0) { // selection = Contacts.DISPLAY_NAME + " LIKE ?"; // selectionArgs = new String [] {"%" + constraint + "%"}; // } numLoaderManagersRunning++; String constraint = null; if(args != null){ constraint = args.getString(CONSTRAINT); } Uri uri = null; if(constraint!= null && !constraint.isEmpty()){ uri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, constraint); } else { uri = Contacts.CONTENT_URI; } return new CursorLoader(getActivity(), uri , PROJECTION_PHONEBOOK_CONTACTS, null, null, Contacts.DISPLAY_NAME + " COLLATE NOCASE ASC"); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { Logger.e(TAG, "Load finished "); // phoneBookContactsCursorAdapter.swapCursor(new MatrixCursor(new String [] { Contacts._ID, // Contacts.DISPLAY_NAME, Contacts.PHOTO_ID })); phoneBookContactsCursorAdapter.swapCursor(data); if (actualMultiFilterListener != null){ actualMultiFilterListener.onFilterComplete(data.getCount()); } //filterList(""); numLoaderManagersRunning--; if (numLoaderManagersRunning <= 0) { // The list should now be shown. if (isResumed()) { setListShown(true); } else { setListShownNoAnimation(true); } } } @Override public void onLoaderReset(Loader<Cursor> loader) { // phoneBookContactsCursorAdapter.swapCursor(null); Logger.e(TAG, "Load resetted "); } }; ```