Search code examples
androidandroid-loadermanagerandroid-cursorloader

Race condition with LoaderManager?


I'm using LoaderManager to load a cursor of list of contacts from my phone.

I'm literally using just the example code given here:

http://developer.android.com/reference/android/app/LoaderManager.html

My only change is that instead of using this as an adapter for a main screen listview, I've used it as an adapter for an AutoCompleteTextView. My problem is that when I quickly change the text, either by typing furiously, or holding down the delete button to delete everything in a row, it results in this error:

01-09 02:36:47.248: E/AndroidRuntime(24231): FATAL EXCEPTION: main
01-09 02:36:47.248: E/AndroidRuntime(24231): android.database.StaleDataException: Attempted to access a cursor after it has been closed.
01-09 02:36:47.248: E/AndroidRuntime(24231):    at android.database.BulkCursorToCursorAdaptor.throwIfCursorIsClosed(BulkCursorToCursorAdaptor.java:64)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at android.database.BulkCursorToCursorAdaptor.getColumnNames(BulkCursorToCursorAdaptor.java:159)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at android.database.AbstractCursor.getColumnIndex(AbstractCursor.java:283)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at android.database.AbstractCursor.getColumnIndexOrThrow(AbstractCursor.java:308)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at android.database.CursorWrapper.getColumnIndexOrThrow(CursorWrapper.java:78)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at android.widget.CursorAdapter.swapCursor(CursorAdapter.java:338)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at android.widget.CursorAdapter.changeCursor(CursorAdapter.java:309)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at android.widget.CursorFilter.publishResults(CursorFilter.java:67)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:282)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at android.os.Handler.dispatchMessage(Handler.java:99)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at android.os.Looper.loop(Looper.java:137)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at android.app.ActivityThread.main(ActivityThread.java:5070)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at java.lang.reflect.Method.invokeNative(Native Method)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at java.lang.reflect.Method.invoke(Method.java:511)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:795)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:558)
01-09 02:36:47.248: E/AndroidRuntime(24231):    at dalvik.system.NativeStart.main(Native Method)

Solution

  • It seems that a CursorAdapter has a base implementation of getFilter(), which returns the current cursor after going through runQueryOnBackgroundThread(). This works fine for the original Android developer example for the LoaderManager, since ListViews don't use the filter at all. However, AutoCompleteTextViews do use the filter, so I was essentially running two async threads that were trying to load cursors, and was not thread-safe.

    Since Filter already provides the same asynchronous loading functionality that LoaderManager does, I just did away with the LoaderManager and load through the Filter by overriding CursorAdapter.runQueryOnBackgroundThread().