I have a ListView in a dialog and an EditText to filter my list for CustomerCodes,i've implemented my filter query with a TextWatcher and in onTextChanged() i've changed my old Cursor with
Cursor FilteredCPCodeList = CustomerDBAdapter.instance.CursorFilteredCPCode(s.toString()); //Retrieve Filtered CustomerCodeList
CpListadapter.changeCursor(FilteredCPCodeList);
List Filtering works Perfect with codes above but when i click on a ListItem it's OnItemClickListener which use old Cursor cause an Exception which tells :
01-05 10:33:01.577: E/AndroidRuntime(5380): android.database.StaleDataException: Attempting to access a closed CursorWindow.Most probable cause: cursor is deactivated prior to calling this method.
i know that change cursor will close my old cursor but i don't know how can i use StopManagingCursor on my old cursor(or another soloution) to solve this Issue.i've tried this code on onTextChanged() but it doesn't work either
Cursor OldCursor = CpListadapter.getCursor();
stopManagingCursor(OldCursor );
any help would be appreciated,Thanks
stopManagingCursor() is deprecated and not recommended anymore. You should be using CursorLoader. Then you can use a SimpleCursorAdapter along with the swapCursor(Cursor) method.
If you need to use your current setup, you should be able to do CpListadapter.getCursor().close() (for example, in your onDestroy()).
Related
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.
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);
The reason I'm asking that is because requery() is deprecated. What is the best way now to refresh your ListView?
requery() updates a Cursor, not a CursorAdapter. As you say, it has been deprecated, and its replacement is:
oldCursor = myCursorAdapter.swapCursor(newCursor); // hands you back oldCursor
or:
myCursorAdapter.changeCursor(newCursor); // automatically closes old Cursor
myCursorAdapter.notifyDataSetChanged() notifies the ListView that the data set has changed, and it should refresh itself
Use BaseAdapter.notifyDataSetChanged().
You can create a new cursor and call changeCursor() (documentation here) on your CursorAdapter instance or call notifyDataSetChanged() (documentation here) on your adapter.
This is what works for me, im not sure its the best way.
c = db.rawQuery( "SELECT * FROM mytable", null); //same line of the first initialization
adapter.swapCursor(c);
I refresh the only cursor, I dont know what to do with a new one.
Also i dont know pepole that answer with only a name of a function.
The only thing that helped me, was to initialise new cursor, similar as previous one like:
cursor = dbHelper.myDataBase.rawQuery(StaticValues.SQL_CAT, null);
newCursor = dbHelper.myDataBase.rawQuery(StaticValues.SQL_CAT, null);
and then call:
adapter.changeCursor(newCursor);
that updated my listview.
I tried to add my response as a comment but failed for some reason.
cursoradapter.notifyDatasetchanged() should not work as your adapter is linked to a "cursor" which holds a "query" that was executed before dataset was changed. Hence one would require to change "cursor" by doing the "new query" and link to cursoradapter using cursoradapter changecursor().
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
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(SQLiteDirectCursorDriver.java:
53)
E/Cursor ( 2318): at
android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:
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