Proper way to handle Cursor objects - android

When you call .close() on a Cursor Object, does it mean that for the rest of the Activity's duration it cannot be used? The following is a method within my Manager Object:
Cursor cursor = null;
try {
SQLiteDatabase db = openDb();
cursor = db.query("table", null, "id=?", new String[] { id }, null, null, null);
cursor.moveToFirst();
long dateTime = cursor.getLong(1);
cursor.close();
return dateTime ;
} catch (CursorIndexOutOfBoundsException e) {
return -1;
} finally {
if (cursor != null) {
cursor.close();
}
closeDb();
}
This is the method that's throwing me an IllegalStateException. However, there's a slight twist: it only throws an error the second time it is called. Tracing the stacktrace, I find that the line causing me trouble is the following:
Cursor cursor = db.query("table", null, "id=?", new String[] { id }, null, null, null);
Just to clear things up a bit, this method can be called several times within the Activity's lifetime through clicking of a particular ListView item. The openDb() and closeDb() methods are as follows:
public SQLiteDatabase openDb() {
if (mDbHelper == null) {
mDbHelper = new DatabaseHelper(mContext);
}
return mDbHelper.getWritableDatabase();
}
public void closeDb() {
mDbHelper.close();
}
And these are stored in the superclass of my Manager object. mDbHelper is a static Object.
Being fairly new to Android programming, I'm wondering why this would throw me an exception. The only logical explanation I can think of is that Cursor Objects are actually re-used, and they should not be closed for the duration of an activity. Am I right? And if I am, when do you actually close the Cursor?
---EDIT---
Having tinkered around with the code a bit, I seem to be getting the exception being thrown on a much more irregular basis. For some odd reason, it seems to happen randomly; I can click on eight multiple ListView items with no issues, and suddenly bam! The ninth causes the application to crash.
Because clicking on a ListView also invokes a method which updates that very same table (which up till now has caused me no problems thus far), I think it's only relevant that I include that as well:
try {
SQLiteDatabase db = openDb();
ContentValues cv = new ContentValues();
cv.put("id", id);
cv.put("dateTime", dateTime);
long affected = db.replace("table", null, cv);
return affected;
} finally {
closeDb();
}
As you can see, no rocket science is involved here. However, this method has now started to throw similar Exceptions, happening on the line:
long affected = db.replace("table", null, cv);
I'm starting to suspect that it's a click-too-fast problem, and the SQLite connections are not given enough time to close. Because there is no pattern to the crashes that I can discern; sometimes it crashes on the third try, sometimes on the eighth, sometimes it even seems to work fine till well past the tenth.
Could that be possible?

As the docs say after you have called close() your Cursor becomes forever invalid.
Also, there's no need to call close 2 times in your function. It's enough to call it in the finally block only

Because you call the close() method on the static object, it may not necessarily "nullify" the static object. So when you check if mDbHelper is null in the openDb() method the second time, it will pass this condition, and therefore the method will unintentionally return a closed database. When you try and query this closed database, it will therefore throw the illegalstateexception.
Try:
public SQLiteDatabase closeDb() {
mDbHelper.close()
mDbHelper = null;
}
I hope I have helped.

Related

attempt to re-open an already-closed object: SQLiteDatabase error on first launch

I am having a big problem with my database. When I try to make the first call to the database since the app launches, I get a crash saying:
attempt to re-open an already-closed object: SQLiteDatabase /data/user/0/<path_to_db>
I am using a DBHelper class which extends SQLiteOpenHelper.
When I launch the app the first time, it should create the DB etc, and then have an empty DB which should still allow me to do some queries on it.
However, when I do my first query, it crashes with the above error.
Here's the query:
public TerminalList getTerminalLocations() {
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.query(LOCATION_TABLE_NAME, null, null, null, null, null, null);
//do stuff
cursor.close();
db.close();
return locationList;
}
Its crashing on the first line!!
SQLiteDatabase db = getReadableDatabase();
It doesn't even get as far as the cursor!
How is this possible? Does anyone know how to fix this?
Does it make a difference that this is being called from the OnCreateView of my fragment? Here is how I call it. Its pretty standard:
private void getLocations() {
DBHelper db = DBHelper.getInstance(getActivity());
TerminalList locations = db.getTerminalLocations();
//do stuff
}
I've tried commenting out this call, but it pushes the problem to the next DB call, so is happening with every single DB call in the app!!
Thanks.
Try something like this for the utility method:
public Cursor getWhatyouNeed(SQLiteDatabase db, params...) {
Cursor yourData = db.query(your_query_params...);
// your logic
return yourData;
}

Do I have to close the Cursor object whenever I call rawQuery()?

I'm planning to use Android SQLite for the first time.
Cursor c = db.rawQuery("some select command here", null);
// some jobs with the cursor..
c = db.rawQuery("another select command here", null);
// some jobs with the cursor..
c.close();
db.close();
c = null;
db = null;
As you can see, I'm trying to call rawQuery() method several times.
Do I have to close the cursor before I call rawQuery() method AGAIN?
Do I have to assign null to the variables after closing the cursor and database like above?
Do I have to close the cursor before I call rawQuery() method AGAIN?
Close the cursor whenever you are finished reading from it. This is to release resources that are opened by the cursor, so yes, you should close the first cursor before the second query.
Do I have to assign null to the variables after closing the cursor and database like above?
It depends on the scope of the variables. If your code looks like this...
class Foo {
void doSomething() {
SQLiteDatabase db = ...
Cursor c = db.rawQuery...
// other stuff
c.close();
db.close();
}
}
... then there's really no point nulling them out because they will go out of scope immediately when the method finishes execution. But your actual code might look different. If you have some reason for wanting to allow those objects to be garbage collected, then you can null out the variables.

App crashes between onPause and onResume Listview issue

I have a listview activity which populates data through an sqlite database; however, whenever I enter onPause and then go into onResume my app crashes and I receive this error: "java.lang.IllegalStateException: trying to requery an already closed cursor android.database.sqlite.SQLiteCursor#418106a8". Would anyone know how to stop this? Is there a method I have to call in onPause?
#Override
protected void onResume() {
super.onResume();
uGraduateListAdapter = new ArrayAdapter<String>(ListOfAlarms.this, android.R.layout.simple_list_item_1, populateList());
listOfAlarms.setAdapter(uGraduateListAdapter);
Log.i(TAG, "Resume was called");
}
#Override
protected void onPause() {
super.onPause();
Log.i(TAG, "Pause was called");
sqliteDatabase.close();
}
public List<String> populateList(){
// We have to return a List which contains only String values. Lets create a List first
List<String> uGraduateNamesList = new ArrayList<String>();
// First we need to make contact with the database we have created using the DbHelper class
AndroidOpenDbHelper openHelperClass = new AndroidOpenDbHelper(this);
// Then we need to get a readable database
sqliteDatabase = openHelperClass.getReadableDatabase();
// We need a a guy to read the database query. Cursor interface will do it for us
//(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
cursor = sqliteDatabase.query(AndroidOpenDbHelper.TABLE_NAME_ALARM, null, null, null, null, null, null);
// Above given query, read all the columns and fields of the table
startManagingCursor(cursor);
// Cursor object read all the fields. So we make sure to check it will not miss any by looping through a while loop
while (cursor.moveToNext()) {
// In one loop, cursor read one undergraduate all details
// Assume, we also need to see all the details of each and every undergraduate
// What we have to do is in each loop, read all the values, pass them to the POJO class
//and create a ArrayList of undergraduates
String alarmName = cursor.getString(cursor.getColumnIndex(AndroidOpenDbHelper.COLUMN_NAME_ALARM_NAME));
// String ugUniId = cursor.getString(cursor.getColumnIndex(AndroidOpenDbHelper.COLUMN_NAME_UNDERGRADUATE_UNI_ID));
String alarmTotalTime = cursor.getString(cursor.getColumnIndex(AndroidOpenDbHelper.COLLUMN_ALARM_TOTALTIME));
// Finish reading one raw, now we have to pass them to the POJO
TestAlarm ugPojoClass = new TestAlarm();
ugPojoClass.setTitle(alarmName);
ugPojoClass.setTotalTime(alarmTotalTime);
// Lets pass that POJO to our ArrayList which contains undergraduates as type
pojoArrayList.add(ugPojoClass);
// But we need a List of String to display in the ListView also.
//That is why we create "uGraduateNamesList"
uGraduateNamesList.add(alarmName);
}
// If you don't close the database, you will get an error
sqliteDatabase.close();
return uGraduateNamesList;
}
You are using deprecated methods (startManagingCursor()), which is dangerous.
How I see what happens: when you close your database (twice actually: in populateList() and onPause()), your cursors to this database become invalid. But since you called startManagingCursor(), your Activity retains your cursors and tries to call requery() on them when restarting, which throws the error.
Try not calling startManagingCursor() at all, just cursor.close() when you're done with it. Or you can migrate to newer LoaderManager altogether.

How does a cursor used by a RemoteViewService get deactivated

I get ACRA exception reports from some users that the cursor which supplies data to my appwidget (RemoteViewService) is deactivated/closed. It never happens to me in person, but it happens enough where it's a bit of an issue.
Here's the code to my RemoteViewService:
public static class ListItemService extends RemoteViewsService {
public RemoteViewsFactory onGetViewFactory(final Intent intent) {
return new RemoteViewsFactory() {
private MyCursor cursor;
public void onCreate() {
// Nothing
}
public synchronized void onDestroy() {
if (this.cursor != null)
this.cursor.close();
}
public synchronized RemoteViews getViewAt(int position) {
// Here I read from the cursor and it crashes with
// the stack trace below
}
public int getCount() {
return ((this.cursor != null) ? this.cursor.getCount() : 0);
}
public int getViewTypeCount() {
return 1;
}
public boolean hasStableIds() {
return true;
}
public long getItemId(int position) {
return position;
}
public RemoteViews getLoadingView() {
return null;
}
public synchronized void onDataSetChanged()
{
if (this.cursor != null)
this.cursor.close();
this.cursor = getApplicationCntext().getContentResolver().query(myUri, null, null, null, null);
}
};
The stack trace varies from platform version to platform version. For example, I get the following on 4.0.3:
android.database.StaleDataException: Attempting to access a closed CursorWindow.Most probable cause: cursor is deactivated prior to calling this method.
On 2.3, I get a:
java.lang.IllegalStateException: attempt to re-open an already-closed object: android.database.sqlite.SQLiteQuery
I cannot, for the life of me, figure out who or what closes the cursor on me, other than from onDestroy() and onDataSetChanged(). Some users had reported that they weren't actively working with app when the crash happened.
I suspected maybe multiple calls to the ContentProvider return the same cursor and when I display my UI which uses the same query, they step on each other. But this doesn't appear to be the case as the cursor objects are different.
Any ideas?
Looks to me like a sync issue between multiple threads, one closing the previous cursor and one immediately accessing it thereafter.
You may want to consider closing the cursor right when you first can, such as a onPause event.
Also - you can add a safety precaution as checking cursor.isClosed() before you access it again. You can also add some synchronisation to your code.
A helper method that fetches the new cursor with a local var and only once it's done, replaces the previous one and closes it may be a quicker solution in the meantime.
From AOSP doc;
This interface provides random read-write access to the result set
returned by a database query. Cursor implementations are not required
to be synchronized so code using a Cursor from multiple threads should
perform its own synchronization when using the Cursor.
From Content provider basics,
The ContentResolver.query() client method always returns a Cursor containing the columns specified by the query's projection for the rows that match the query's selection criteria. A Cursor object provides random read access to the rows and columns it contains. Using Cursor methods, you can iterate over the rows in the results, determine the data type of each column, get the data out of a column, and examine other properties of the results. Some Cursor implementations automatically update the object when the provider's data changes, or trigger methods in an observer object when the Cursor changes, or both.
Try to add this.cursor = null on onDestory() method
public synchronized void onDestroy() {
if (this.cursor != null){
this.cursor.close();
this.cursor = null
}
}

android database already close onResume

my application has an issue where if I go back to an activity I get an error that the database has been closed:
ERROR/AndroidRuntime(3566): Caused by: java.lang.IllegalStateException: database /data/data/com.kempville.app/databases/MyDB already closed
I instantiate, open, instatiate a cursor, do the query, close the cursor and close the database all within a method called during onResume(). I don't know what is assumed to be open whenever onResume gets called when this activity comes back to the front.
private void getMydata() {
MyDb db;
db = new MyDB(this);
db.open();
Cursor c = db.getInfo(code);
startManagingCursor(c);
if (c.moveToFirst()) {
name = c.getString(c.getColumnIndex("name"));
}
c = fdb.getType(myArray.getString("type"));
startManagingCursor(c);
if (c.moveToFirst()) {
type = c.getString(c.getColumnIndex("type"));
}
c.close();
db.close();
Seems that startManagingCursor will try to close it, though you've closed it yourself. Either drop the startManagingCursor (it's getting deprecated) or better call stopManagingCursor

Categories

Resources