How can I make my Cursor survive an orientation change? - android

I am trying to make my app rotation friendly, but I am having some problems saving the cursor.
The cursor holds about 13k+ rows of data displayed in a ListView, and thus would take quite a while if I would do a requery every time the configuration changes. In my onRetainNonConfigurationInstance(), I am returning my Cursor and then retrieving it through getLastNonConfigurationInstance().
However, my retrieved cursor seems to be closed already, and thus my adapter cannot render the list anymore. From what I understand, the cursor was closed since onDestroy() automatically closes all cursors.
I save the Cursor like this:
#Override
public Object onRetainNonConfigurationInstance() {
return myCursor;
}
And retrieve it like this:
myCursor = (Cursor)getLastNonConfigurationInstance();
if (myCursor == null) {
// Do some stuff here (access DB, etc)
} else { // we are returning from configuration change
// Feed the cursor to the adapter
}
I am pasting the stack trace if someone wants to look at it:
01-25 16:57:45.637: ERROR/AndroidRuntime(12976): android.database.StaleDataException: Access closed cursor
01-25 16:57:45.637: ERROR/AndroidRuntime(12976): at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:217)
01-25 16:57:45.637: ERROR/AndroidRuntime(12976): at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:41)
01-25 16:57:45.637: ERROR/AndroidRuntime(12976): at com.test.sample.helper.DictionaryAdapter.bindView(DictionaryAdapter.java:35)
[........More ListView-related errors here..........]
I stepped through the code and I found out that as far as onRetainNonConfigurationInstance(), the cursor is still open, but after getting it through getLastNonConfigurationInstance() it is already closed.
How can I make my Cursor survive the orientation change? Thank you for the help!
EDIT:
Based on Romain's answer, I commented out all my startManagingCursor()s. I should have connected the dots and thought about it! Anyway, my app now survives one rotation, but flipping it back to the original orientation still crashes it. Continuing my debugging and will let you know what I find out.
EDIT2:
I think I may have found what is causing the new errors. I have implemented a FilterQueryProvider which returns a new Cursor. What I did was assign the results of that filter to my original Cursor. Seems to work so far.

You are probably using a managed cursor. Managed cursors are automatically closed when the Activity is destroyed. You should switch to an unmanaged Cursor.

Just add this attribute in your activity tag in the manifest file
android:configChanges="orientation|keyboardHidden"
it will resolve this
no need to implement anything else
:)
It helped me though

Related

Is it actually unsafe to call stopLoading on a Loader that is being managed?

The android documentation for Loader#stopLoading() says:
When using a Loader with LoaderManager, you must not call this method
yourself, or you will conflict with its management of the Loader.
But is that really true? Specifically I am interested in CursorLoader. I looked the through the Android source for version 4.2 and it seems pretty benign. Has anyone tried using this method and seen a problem? Is there an alternative if I want to keep the current active Cursor last delivered by the CursorLoader and also stop it temporarily from reloading due to the internal ContentObserver being triggered? Basically I want to make a bunch of changes to the ContentProvider that is the source of this managed Cursor and I don't want the Loader kicking off a ton of loads until I am done.
I agree with a.ch that relying on the Android source code isn't the way to go not just because you risk breaking your app with future updates but also because of the hundreds of modified versions of Android (the joy of fragmentation). stopLoading() wouldn't work anyway, for reasons see below.
cancelLoad() will not close the underlying cursor (where did you get that from?) so while this method will cancel a load if one is in progress, it won't prevent future loads. And even if it would prevent future loads it wouldn't work either (for the same reasons stopLoading() won't work).
My initial idea to solve this was to implement a custom CursorLoader and just override the onContentChanged() method using a flag to decide whether to "take" the changes or not. This way one could temporarily disable the update:
class MyCursorLoader extends CursorLoader {
private boolean mDontUpdate;
public MyCursorLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
super(context, uri, projection, selection, selectionArgs, sortOrder);
}
#Override
public void onContentChanged () {
if (! mDontUpdate) {
super.onContentChanged();
}
}
#Override
public boolean takeContentChanged () {
return mDontUpdate ? false : super.takeContentChanged();
}
void dontUpdate(boolean dontUpdate) {
mDontUpdate = dontUpdate;
}
}
You would use this class instead of the CursorLoader in the onCreateLoader(int, Bundle) and you would set the don't update flag during the database update.
Now that works nicely except it doesn't. While it's not clear from the question what the CursorLoader and the resulting Cursor are used for I assume the Cursor is used in a CursorAdapter for a ListView or a similar widget.
The widget registers its own ContentObserver to the Cursor and updates the views accordingly. So even if no new Cursor is loaded a ListView (a GridView or any other widget attached to the Cursor) would still update (Android would call the newView, bindView methods in a CursorAdapter).
This brings me to my next approach.
Although it's not clear from your question I assume you are doing some modifications to a database? If that's the case then why not do all the modifications in a single transaction? If done in one transaction changes wouldn't show anywhere before the transaction is committed and that's probably what you want.
If your Cursor isn't backed by a database though you would probably have to remove the Adapter from your widget during the update.
First of all, you shouldn't rely on the android source code: if there are no any issues with the current android build, it doesn't mean there won't be any in future. I'd suggest considering android system as a black box, this will let you write robust and reliable apps.
According to your question on the alternative, consider cancelLoad().

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?
If you've started using swapCursor() instead of changeCursor() everywhere, then I hope you've also started handling cursor closing in those places.
changeCursor() will close the old cursor, this is intentional and works flawlessly when you're just directly using the cursor provided by onLoadFinished(). It is done this way so you don't have to worry about closing it.
When you rotate your device, the android system will check that the cursor it sent you last time has not yet been closed, and sends it again rather than spending resources on creating a new one. Your code wraps this cursor in a new instance of a MergeCursor which gets passed to changeCursor(), which sees that this is not the same object it got before, and decides to close the old instance. Since MergeCursor only wraps the cursors you pass it, rather than copying the data in them, your new instance now contains (at least) one closed cursor.
To correctly handle this you'll need to write some code of your own that checks if the cursor you get through onLoadFinished() is the same as one you have in your current MergeCursor instance, and only close the existing instance if you are getting a new cursor. Of course you'll also need to keep track of the other cursors wrapped in the same MergeCursor instance.
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.

What is the recommended way to manage cursors in Android with SimpleCursorTreeAdapter?

I have an class that extends ExpandableListActivity. To fill the data for this list, I'm using a class that extends SimpleCursorTreeAdapter. I have a group (top-level) cursor that looks something like this:
mGroupCursor = ((MyApplication)getApplication()).
getDatabaseHelper().getGroupCursor();
startManagingCursor(mGroupCursor);
Of course, I also need to provide the child cursors:
#Override
protected Cursor getChildCursor(Cursor groupCursor)
{
Cursor childCursor = ((MyApplication)getApplication().
getDatabaseHelper().
getChildCursorForGroup(groupCursor.getInt(mGroupIdIndex));
startManagingCursor(childCursor);
return childCursor;
}
I was under the impression that I could call startManagingCursor on these cursors and everything would be taken care of. Apparently that is not the case, because leaving the list view and coming back with the back button throws an exception, "unable to resume activity: attempting to requery closed cursor" or something like that.
Removing the calls to startManagingCursor() fixes the problem, although I think that this is not really the right way to do things because in this case I'm not closing the cursors anywhere.
What is the recommended way to handle these cursors? I'm looking for something simple and lightweight. My database is a local SQLite database and is relatively small, so as long as the performance isn't unreasonably slow it's not an issue for me.
startManagingCursor is obsolete. I'd look at using an AsyncTask to get the cursors in the background, and set the observers yourself.

What does "invalid statement in fillWindow()" in Android cursor mean?

I sometimes see this error in my logcat output,
Cursor: invalid statement in fillWindow().
It sometimes happens when I press the back key and then it goes to the default Android listview before going to my custom listview.
What does it mean? How do I solve it? Because it does not point to any line of code where the problem is coming from.
When dealing with ListActivities, this issue has to do with the Cursor objects, CursorAdapter objects, and Database objects not being closed properly when the Activity stops, and not being set properly when the Activity starts or resumes.
I had to make sure that I closed my SimpleListAdapter, my Cursors, and then my Database objects in that respective order, in the onStop method of the Activity that is called when the TabActivity resumes.
I had already been closing the Cursor and Database objects, but had not been closing my SimpleListAdapter Cursor.
/**
* onStop method
*
* Perform actions when the Activity is hidden from view
*
* #return void
*
*/
#Override
protected void onStop() {
try {
super.onStop();
if (this.mySimpleListAdapterObj !=null){
this.mySimpleListAdapterObj.getCursor().close();
this.mySimpleListAdapterObj= null;
}
if (this.mActivityListCursorObj != null) {
this.mActivityListCursorObj.close();
}
if (this.myDatabaseClassObj != null) {
this.myDatabaseClassObj.close();
}
} catch (Exception error) {
/** Error Handler Code **/
}// end try/catch (Exception error)
}// end onStop
It is of utmost importance that you close the Cursors, Databases, DBHelpers in the right order.
for e.g.
for the given code below.
DBHelper dbhelper = new DBHelper();
SQLiteDataBase db = dbhelper.getWritableDatabase();
Cursor c = db.query(/*some parameters*/);
the order of closing should be like:
c.close();
db.close();
dbhelper.close();
Otherwise different errors keep on spawning and the developer does not even come to know about it. "Cursor: invalid statement in fillWindow()" being one of such errors.
Maybe this can help you: http://www.ragtag.info/2011/feb/1/database-pitfalls/
It seems that calls to getReadableDatabase and getWritableDatabase returns the same connection to the database (even if you made several calls to them).
So, any call to close() on any of them will close both connection(s).
If you tries to use a cursor later, you'll get the nice 'Invalid statement', since the connection which the cursor relies on, is already closed.
If you are using a custom Class instance e.g. Model m that holds a DatabaseManager, which in turn holds a SQLiteDatabase: Model->DatabaseManager->SQLiteDatabase
Then, if you do a query to m (which does the appropiate delegations) and then you do something like m.close() (which actually closes the SQLiteDatabase) and after that you try to use the Cursor you will get that error.
The solution is: first use the cursor and then close the Db.
My response is based in the 2 existing so far, that inspired me to solve the problem.
I am still having issues with the 'Invalid statement in fillWindow()' error.
I have narrowed the issue down to the SimpleCursorAdapter cursor for my ListView.
For example, if I am in the listview for an Activity A, and I close the cursor before starting a new Activity B, I don't get the 'Invalid statement in fillWindow()' when I return to Activity A.
However, before Activity B loads, I see the list from Activity A's listview disappear on the screen, and the 'No Records Found' message is displayed briefly before the screen is hidden, before Activity B's screen is shown.
How can I gracefully resolve this issue?
EDIT:
I actually figured this out this morning. I added
this.stopManagingCursor(this.myListCursor);
to the onPause method in my ListActivity classes, and that resolved the 'Invalid statement in fillWindow()' error.

Finalizing a Cursor that has not been deactivated or closed non-fatal error

i'm getting a "Finalizing a Cursor that has not been deactivated or
closed" error on this piece of code.
The code is used to fill a listview.
Since it's a non-fatal error , there is no crash and all seems to works
fine..but i don't like the error.
If i close the cursor at the end of this code..the listview stay's
empty.
if i close the cursor in onStop , i get the same error.
How do i fix this??
private void updateList() {
DBAdapter db = new DBAdapter(this);
db.open();
//load all waiting alarm
mCursor=db.getTitles("state<2");
setListAdapter(new MyCursorAdapter(this, mCursor));
registerForContextMenu(getListView());
db.close();
}
error :
E/Cursor ( 2318): Finalizing a Cursor that has not been deactivated
or closed. database = /data/data/xxxxxxxxxxxxxxx.db, table = alerts,
query = SELECT _id, alert_id,
E/Cursor ( 2318):
android.database.sqlite.DatabaseObjectNotClosedException: Application
did not close the cursor or database
object that was opened here
E/Cursor ( 2318): at
android.database.sqlite.SQLiteCursor.<init>(SQLiteCursor.java:210)
E/Cursor ( 2318): at
android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDr­iver.java:
53)
E/Cursor ( 2318): at
android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.j­ava:
1345)
E/Cursor ( 2318): at
android.database.sqlite.SQLiteDatabase.queryWithFactory(SQLiteDatabase.java­:
1229)
....
....
You should not be getting that message if you close the Cursor in onStop() or onDestroy(). Please try that again. Or, call startManagingCursor() after you get the Cursor from your query, and Android will close the Cursor on its own.
Scott,
I ran into the same problem as you. Before you close your database, i.e. "db.close()," make sure that your cursors are closed first, i.e. "mCursor.close()"
Like so:
private void updateList()
{
DBAdapter db = new DBAdapter(this);
db.open();
//load all waiting alarm
mCursor=db.getTitles("state<2");
setListAdapter(new MyCursorAdapter(this, mCursor));
registerForContextMenu(getListView());
// Let's close the cursor.
mCursor.close();
db.close();
}
You mentioned that if you closed your cursor your list view stays empty. I recommend you pass the information over to a class and copy it over (allocate the memory) then close the cursor.
When a query returns a cursor it is actually positioned "before" the first
record in the cursor. An adapter will attempt do a 'getItem' at the first
element so it will fail as the cursor is not positioned at any.
In my base adapters I do a cursorMoveToPosition on the getViews. This seems
to eliminate the need for the movefirst.
Do not use startManagingCursor() since that is no longer the recommended approach. The issue occurs because a cursor / DB connection is still not closed by the time the finalizer gets to this object. You can avoid that by either allowing a loader to manage the cursor or by tracking all cursor / DB / SQLiteOpenHelper connections yourself and cleaning up after them.
Using a Loader is fairly cumbersome and requires a lot of moving parts for it to work in conjunction with say a list view. On the other hand tracking your cursor and database connections is prone to human error. If the number of cursor / DB objects is low, I'd recommend the latter solution. If not, let a loader handle your connections.
Close the cursor object wherever you are creating it.
When you create a cursor object and done with traversing through a SQLite table then close it after it has been used. This closing of cursor prevents exception in logcat.
You will not get any exception related to finalizing the cursor that left opened.
This fixed same issue in my Application.
I struggled with this problem for two days. I was trying to get sample code working which passed a cursor, returned from a database query, directly to List Adapter - no interim adapter. It refused to work - just displayed a blank screen - until I invoked 'moveToFirst()' on the cursor before passing it to the ListAdapter. Go figure! When I comment this out, it breaks.
Just thought that I'd share this to save people the same struggles that I had.
If anyone can shed some light on why this is so, I'd appreciate it. I haven't had to invoke moveToFirst on cursors up to now, to get them to perform properly.
Just had the same problem and thought to let you know - just in case....
I accidentally called my fetch routine two times and hereby "lost" the resulting cursor of the first call. This caused the error.
I too have been having issues with the closing the cursor:
Closing the cursor right after setting the list view's adapter causes the cursor to close before the data gets displayed.
Can't use startManagingCursor to manage the cursor because it has been deprecated.
The new cursorLoader replacement for startManagingCursor seems to be overkill.
Moving the cursor's position as suggested did not work.
Making the task an inner class of the activity and closing the cursor in the activity's onDestroy method works sometimes but not all the time.
Making the task an inner class of the activity and closing the cursor in the activity's onStop method seems to be working.
I also found out that I can close the database and the sqlite open helper before closing the cursor. I can even close them right after setting the list view's adapter. The data will still display.
startManagingCursor(cursor);
This has fixed my problem

Categories

Resources