title: Error when using CursorLoader to load data from ContentProvider
body: <p>I'm building an Android app which uses OSM data to provide routes for users from a set of given locations. The user can type where they wish to go into a SearchView and as the user types the search results are filtered to narrow the results, they can then choose a destination from the drop down ListView. This filtering is done using onQueryTextChange(). I'm using a ContentProvider to query this data from a database and implementing the LoaderManager.LoaderCallbacks interface to requery the ContentProvider and provide new data for the adapter to use. </p>
  
  <p>All of this works the vast, vast majority of the time and performs exactly as expected. However ,very rarely, the app will crash with the following stack trace.  </p>
  
  <pre><code>java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteQuery: SELECT _id, suggest_text_1, suggest_intent_data FROM Locations WHERE (suggest_text_1 LIKE ?)
      at android.database.sqlite.SQLiteClosable.acquireReference(SQLiteClosable.java:55)
      at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:58)
      at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:152)
      at android.database.sqlite.SQLiteCursor.onMove(SQLiteCursor.java:124)
      at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:214)
      at android.database.CursorWrapper.moveToPosition(CursorWrapper.java:162)
      at android.support.v4.widget.CursorAdapter.getItemId(CursorAdapter.java:225)
      at android.widget.AdapterView.rememberSyncState(AdapterView.java:1195)
      at android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:811)
      at android.widget.AbsListView$AdapterDataSetObserver.onChanged(AbsListView.java:6280)
      at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
      at android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50)
      at android.support.v4.widget.CursorAdapter.swapCursor(CursorAdapter.java:347)
      at android.support.v4.widget.SimpleCursorAdapter.swapCursor(SimpleCursorAdapter.java:326)
      at android.support.v4.widget.CursorAdapter.changeCursor(CursorAdapter.java:315)
      at android.support.v4.widget.CursorFilter.publishResults(CursorFilter.java:68)
      at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:282)
      at android.os.Handler.dispatchMessage(Handler.java:102)
      at android.os.Looper.loop(Looper.java:136)
      at android.app.ActivityThread.main(ActivityThread.java:5017)
      at java.lang.reflect.Method.invokeNative(Native Method)
      at java.lang.reflect.Method.invoke(Method.java:515)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
      at dalvik.system.NativeStart.main(Native Method)
  </code></pre>
  
  <p>Usually this error occurs when I clear the SearchView to type in another search and other times it occurs as the user is typing. </p>
  
  <p>Edit: Included a partial ContentProvider, omits insert, delete and update.</p>
  
  <pre><code>public class SearchContentProvider extends ContentProvider {
      private DbHelper helper;
  
      private static final String AUTH = "com.dgh1.Navigation.SearchContentProvider";
      private static final String LOCATIONS_PATH = "Location";
      private static final String GEOFENCES_PATH = "Fences";
      private static final String PEOPLE_PATH = "People";
      public static final Uri LOCATION_URI = Uri.parse("content://" + AUTH + "/" +     LOCATIONS_PATH);
      public static final Uri GEOFENCE_URI = Uri.parse("content://" + AUTH + "/" + GEOFENCES_PATH);
      public static final Uri PEOPLE_URI = Uri.parse("content://" + AUTH + "/" +   PEOPLE_PATH);
  
      private static final int LOCATIONS = 10;
      private static final int NAME_LOCATION = 11;
      private static final int NODE_LOCATION = 20;
      private static final int GEOFENCES = 30;
      private static final int MARKERS = 40;
      private static final int GEOFENCE = 50;
      private static final int PEOPLE = 60;
      private static final int PERSON = 70;
  
      private static final String URI_ERROR = "Unknown URI: ";
  
      private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
  
      static {
          matcher.addURI(AUTH, LOCATIONS_PATH, LOCATIONS);
          matcher.addURI(AUTH, GEOFENCES_PATH, GEOFENCES);
          matcher.addURI(AUTH, PEOPLE_PATH, PEOPLE);
          matcher.addURI(AUTH, LOCATIONS_PATH + "/#", NODE_LOCATION);
          matcher.addURI(AUTH, GEOFENCES_PATH + "/#", GEOFENCE);
          matcher.addURI(AUTH, PEOPLE_PATH + "/#", PERSON);
          matcher.addURI(AUTH, LOCATIONS_PATH + "/*", NAME_LOCATION);
      }
  
      @Override
      public boolean onCreate() {
          helper = new DbHelper(getContext());
          return true;
      }
  
      @Override
      public Cursor query(Uri uri, String[] projection, String selection, String[]  selectionArgs, String sortOrder) {
          Cursor cursor = null;
          int uriType = matcher.match(uri);
  
          switch (uriType) {
              case LOCATIONS:
                  if ( selectionArgs == null ) {
                      cursor = helper.getAllLocations();
                  } else {
                      cursor = helper.getSuggestionsData(projection, selection,     selectionArgs);
                  }
                  break;
              case NODE_LOCATION:
                  cursor = helper.getSingleLocationById(uri.getLastPathSegment());
                  break;
              case NAME_LOCATION:
                  cursor = helper.getSingleLocationByName(selectionArgs[0]);
                  break;
              case MARKERS:
                  cursor = helper.getAllMarkers();
                  break;
              case GEOFENCES:
                  cursor = helper.getAllFences();
                  break;
              case PEOPLE:
                  if ( selectionArgs == null ) {
                      cursor = helper.getAllPeople();
                  } else {
                      cursor = helper.findPersonById(selectionArgs[0]);
                  }
                  break;
              default:
                  Log.d(URI_ERROR, uri.toString());
          }
          cursor.setNotificationUri(getContext().getContentResolver(), uri);
          return cursor;
      }
      .
      .
      .
  }
  </code></pre>
  
  <p>Initialsing the loader and set the adapter:</p>
  
  <pre><code>public boolean onCreateOptionsMenu(Menu menu) {
      // Inflate the menu; this adds items to the action bar if it is present.
      getMenuInflater().inflate(R.menu.options_menu, menu);
  
      SearchManager manager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
      MenuItem item = menu.findItem(R.id.action_search);
      SearchView view = (SearchView) MenuItemCompat.getActionView(item);
      view.setIconified(false);
      view.setSearchableInfo(
              manager.getSearchableInfo(getComponentName()));
      view.setSuggestionsAdapter(adapter);
      view.setOnQueryTextListener(this);
  
      getSupportLoaderManager().initLoader(1, null, this);
  
      return true;
  }
  </code></pre>
  
  <p>LoaderManager.LoaderCallbacks implementation:</p>
  
  <pre><code>@Override
  public Loader&lt;Cursor&gt; onCreateLoader(int i, Bundle bundle) {
      return new CursorLoader(this, SearchContentProvider.CONTENT_URI, new String[] { DbHelper.ID, DbHelper.LOCATION_NAME, DbHelper.LOCATION_NODE_ID },
                                                              DbHelper.LOCATION_NAME + " LIKE ?", new String[] { "%" + cursorFilter + "%"}, null );
  }
  
  @Override
  public void onLoadFinished(Loader&lt;Cursor&gt; cursorLoader, Cursor cursor) {
      if ( !(cursor.isClosed()) )
          adapter.swapCursor(cursor);
      }
  }
  
  @Override
  public void onLoaderReset(Loader&lt;Cursor&gt; cursorLoader) {
      adapter.swapCursor(null);
  }
  </code></pre>
  
  <p>OnQueryTextchange():</p>
  
  <pre><code>@Override
  public boolean onQueryTextChange(String s) {
      cursorFilter = !TextUtils.isEmpty(s) ? s : null;
      getSupportLoaderManager().restartLoader(0, null, this);
      return true;
  }
  </code></pre>
  
  <p>Declarations from DbHelper:</p>
  
  <pre><code>public static final String LOCATION_TABLE = "Locations";
  public static final String LOCATION_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1;
  public static final String LOCATION_NODE_ID = SearchManager.SUGGEST_COLUMN_INTENT_DATA;
  </code></pre>
  
  <p>Declarations from onCreate():</p>
  
  <pre><code>adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, null, new String[] { DbHelper.LOCATION_NAME },
                                              new int[] { android.R.id.text2 }, 0);
  </code></pre>
  
  <p>My question is has anyone experienced this before, and if so, have you found a solution ? Or am I simply wrong in my implementation ?</p>