Search code examples
androidstaledataexceptionmatrixcursormergecursor

Merging cursors during onLoadFinished() causes StaleDataException after rotation


I'm loading some results from a database using a loaderManager. Unfortunately, the following code produces a StaleDataException after rotating the device:

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor)
{
    // If we've returned results, then pass them and the webSearches cursor along to be displayed
    if(cursor.moveToFirst())
    {
        // Get a cursor containing additional web searches and merge it at the end of the results cursor 
        MatrixCursor searchesCursor = getWebSearchesCursor(searchTerm, false);
        Cursor[] cursors = { cursor, searchesCursor };
        // TODO: Figure out why merging cursors here causes staledataexception after rotation
        Cursor results = new MergeCursor(cursors);
        // Display the cursor in the ListView
        adapter.changeCursor(results);
    }
    // If no results were returned, then return suggestions for web searches
    else
    {
        // Get a cursor containing additional web searches 
        MatrixCursor noResults = getWebSearchesCursor(searchTerm, true);
        adapter.changeCursor(noResults);    
    }

    // Show the listView and hide the progress spinner
    toggleListView(SHOW);
}

The call to getWebSearchesCursor() returns a MatrixCursor with some additional search prompts to accompany any returned results. I discovered that changing adapter.changeCursor(results) to adapter.changeCursor(cursor) fixes the error, so it looks like merging a MatrixCursor to the returned cursor produces the error.

My question is, why?

If any results are returned, I'd like to be able to add additional items to the returned cursor so the user has the option to perform their search on a couple of websites. Is there a better way to merge cursors so that I don't get this exception after rotation?


Solution

  • This issue came up again a couple of days ago and I was fortunate enough to stumble upon a solution.

    I found out that I should have been using swapCursor() instead of changeCursor(). According to the Android docs:

    Swap in a new Cursor, returning the old Cursor. Unlike changeCursor(Cursor), the returned old Cursor is not closed.

    ...

    If the given new Cursor is the same instance is the previously set Cursor, null is also returned.

    That last part seemed to be the key. The error mentioned in the question above could be traced back to the CursorAdapter choking on the merged cursor because it was closed when it tried to redraw the fragment after a rotation. By using swapCursor() instead, the CursorAdapter was able to reuse the "old" merged cursor instead of questioning its validity and throwing a StaleDataException.

    I'm making some suppositions here; perhaps someone more knowledgeable in the inner-workings of Android could confirm or deny my reasoning.