Android HoneyComb - Requering already closed cursor - android

I read lots of questions about this problem but I can't find the right direction to my problem, maybe because I'm noob and feeling desperated to find the right way...
As far as I read, the "requering already closed cursor" problem happens since HoneyComb because startManagingCursor is deprecated. And since HoneyComb we should use the CursorLoader that works with ContentProvider (which I don't use and I don't want to), and found that someone wrote a class to use CursorLoader without ContentProvider (here).
On my application, I've a mySQLiteHelper class which I've lots of methods to retrieve data from SQLite like this one:
public Cursor listaBeneficios(String id) {
String where = "proposta=?";
String[] whereArgs = {id};
String[] campos = {"pessoa"};
Cursor c = db.query(TABLE_BENEFICIOS, campos, where, whereArgs, null, null, null, null);
if (c.moveToFirst()) {
return c;
} else {
return null;
}
}
And on my Activity, I could read the data like this:
db = new repositorio(MainActivity.this);
Cursor c = db.listaBeneficios(PROPOSTA);
startManagingCursor(c);
address.setText(c.getString(c.getColumnIndex("address")));
db.close();
But I noticed that it gives the infamous "trying to requery a allready closed cursor" everytime that I hit the back button and return to Activity A.
I've allready tried to stopManagingCursor and close() and not use startManagingCursor.
This is driving me nuts.
Is there any available simple examples of using CursorLoaders ?
Am I able to continue using the methods (ie, the listaBeneficios() above) above if I implement CursorLoaders?
Is this class helpful for me? how can I implement it?
Any kind help is appreciated.

A CursorLoader won't work because it requires a content URI. However, you can always use a Loader or AsyncTaskLoader. The documentation for these classes contains examples for how to use them.

In my case, I was using managedQuery to return a cursor to my activity from the ContentProvider. I simply changed the call to getContentResolver().query and problem solved.
Do a Control H in Eclipse to swap out all the changes you need.

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.

How to requery for a cursor now if requery is deprecated?

As per the title, if we used to call cursor.requery(), but it is now deprecated. How do you call this function now?
This method is deprecated.
Don't use this. Just request a new cursor, so you can do this asynchronously and update your list view once the new cursor comes back.
So how does one request a new cursor and pass it back to the adapter?
Re-initialize cursor when your any DML query execute.
See also this.
Just think about a block where you need to use cursor again and again. Populate it with the query. Work with it and then close before reusing
{
Cursor c =//Populating cursor again
while (c.moveToNext()) {
}
if (c != null) c.close();
}
Use a Loader to manage your cursors. It will return a new Cursor as needed and you can use swapCursor() on the adapter to refresh the data.
I used ListAdapter.notifyDataSetChanged(); this worked only on list (like ArrayList), when the listcontent changed.
Using databaseoutput, it doesn't work anymore. Therefore I took cursor.requery(); which is not only depricated. It also works only, if database entries changed.
But now, I want to change my view (switch between birthdays and dates) by changing my settings in a database, so requery does not recognize it.
For all changes works (a little bit dirty): this.onCreate(null);

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