3

I'm totally new to android programming. I can see that this problem has been raised many times before. However, I am still not able to see what the problem is. I'm trying to connect the data from an SQLite database to a listview. In the ListActivity my onCreate looks like the following:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_init_key);
    getActionBar().setDisplayHomeAsUpEnabled(true); 

    DBHandler db = new DBHandler(this);

    Cursor cursor = db.getKeyPointCursor(1, "Crataegus");
    // the desired columns to be bound
    String[] columns = new String[] { "question1","answer1" };
    // the XML defined views which the data will be bound to
    int[] to = new int[] { R.id.question, R.id.answer };

    SimpleCursorAdapter mAdapter = new SimpleCursorAdapter(this, R.layout.key_list_entry, cursor, columns, to, 0);

    cursor.close();
    this.setListAdapter(mAdapter);

}

It raises the following exception, which I hope somebody can help me with.

09-10 21:52:01.505: W/dalvikvm(10976): threadid=1: thread exiting with uncaught exception (group=0x40c8e1f8)
09-10 21:52:01.510: E/AndroidRuntime(10976): FATAL EXCEPTION: main
09-10 21:52:01.510: E/AndroidRuntime(10976): java.lang.RuntimeException: Unable to start activity ComponentInfo{jem.danskflora/jem.danskflora.InitKeyActivity}: java.lang.IllegalStateException: attempt to re-open an already-closed object: android.database.sqlite.SQLiteQuery (mSql = SELECT * FROM Crataegus WHERE _id=?) 
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1970)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1995)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.app.ActivityThread.access$600(ActivityThread.java:128)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1161)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.os.Handler.dispatchMessage(Handler.java:99)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.os.Looper.loop(Looper.java:137)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.app.ActivityThread.main(ActivityThread.java:4514)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at java.lang.reflect.Method.invokeNative(Native Method)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at java.lang.reflect.Method.invoke(Method.java:511)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:993)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:760)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at dalvik.system.NativeStart.main(Native Method)
09-10 21:52:01.510: E/AndroidRuntime(10976): Caused by: java.lang.IllegalStateException: attempt to re-open an already-closed object: android.database.sqlite.SQLiteQuery (mSql = SELECT * FROM Crataegus WHERE _id=?) 
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.database.sqlite.SQLiteClosable.acquireReference(SQLiteClosable.java:33)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:82)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:164)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:156)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.widget.CursorAdapter.getCount(CursorAdapter.java:196)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.widget.ListView.setAdapter(ListView.java:467)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.app.ListActivity.setListAdapter(ListActivity.java:265)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at jem.danskflora.InitKeyActivity.onCreate(InitKeyActivity.java:30)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.app.Activity.performCreate(Activity.java:4562)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1053)
09-10 21:52:01.510: E/AndroidRuntime(10976):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1934)
09-10 21:52:01.510: E/AndroidRuntime(10976):    ... 11 more
Trung Nguyen
  • 7,442
  • 2
  • 45
  • 87
Greforb
  • 45
  • 1
  • 7

3 Answers3

4

Do not close your cursor in your onCreate method. It is referenced by your adapter, but it is not already used !

Try using a LoaderManager / CursorLoader. It is the new way for handling cursors : How to transition from managedQuery to LoaderManager/CursorLoader?

Here is an example from one of my projects (I did not test this code):

public class AdditionalActivitiesListFragment extends ListActivity implements LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, OnClickListener
{
    private SimpleCursorAdapter adapter;


    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        String[] columns = new String[] { "your", "db", "columns" };
        int[] to = new int[] { R.id.your, R.id.fields, R.id.toMapWith };

        getLoaderManager().initLoader(0x02, null, this);

        adapter = new SimpleCursorAdapter(activity.getApplicationContext(), R.layout.row_layout, null, columns, to, SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
        setListAdapter(adapter);
    }

    public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1)
    {
        return new SimpleCursorLoader(this) {
            @Override
            public Cursor loadInBackground() {
                Cursor c = // Your cursor

                return c;
            }
        };
    }

    public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor)
    {
        adapter.swapCursor(cursor);
    }

    public void onLoaderReset(Loader<Cursor> arg0)
    {
        adapter.swapCursor(null);
    }
}

Source of the SimpleCursorLoader : https://gist.github.com/1217628 (via https://stackoverflow.com/a/7422343/615882 )

Community
  • 1
  • 1
Romain Guidoux
  • 2,943
  • 4
  • 28
  • 48
  • Thanks a lot for your answer! The SimpleCursorLoader you use, is that the one proposed here: http://stackoverflow.com/questions/7182485/usage-cursorloader-without-contentprovider ? – Greforb Sep 11 '12 at 19:11
  • And: In your OnLoadFinished and OnLoaderReset the adapter you use is defined in onCreate. How would you handle that? Sorry for asking, but it really annoys me sometimes that Java doesn't just guess what I think :) – Greforb Sep 11 '12 at 19:21
  • @Greforb Oh yes excuse me, I forgot precising the source of the SimpleCursorLoader. Yes it is that one ! :) The adapter is defined in `onCreate()`, and is an attribute of your activity (in my case it was a ListActivity, hence the `setListAdapter()` call) – Romain Guidoux Sep 11 '12 at 20:12
  • Thanks! And sorry for being unclear. adapter.swapCursor(cursor); and adapter.swapCursor(null); are no go according to Java, since adapter is defined in another method. Could you help me out there? – Greforb Sep 12 '12 at 05:15
  • @Greforb That's why it is an attribute of your Activity, so it is visible in all of its methods. I edit my answer – Romain Guidoux Sep 12 '12 at 07:46
  • Next problem: The onCreateLoader is supposed to return a Loader. Since the SimpleCursorLoader is not such a loader but an AsyncTaskLoader it of course complains about that! I tried to cast the loader into a Loader and into a CursorLoader (which I guess is two ways of saying the same?) with no luck. I also thought of swapping out the "Cursor" in "implements LoaderManager.LoaderCallbacks" with something else, but I don't know what. Any help is greatly appreciated! – Greforb Sep 12 '12 at 18:59
  • @Greforb SimpleCursorLoader is a AsyncTaskLoader yes, but AsyncTaskLoader is a Loader (you can see it here : http://developer.android.com/reference/android/content/AsyncTaskLoader.html ), so SimpleCursorLoader is a Loader. But I think I understand your error. The LoaderManager, LoaderManager.LoaderCallbacks and Loader classes I use are from the Android's support library (packages android.support.v4.*)! There are also such classes in android.content.Loader, android.app.LoaderManager and android.app.LoaderManager.LoaderCallbacks – Romain Guidoux Sep 12 '12 at 19:55
  • That's why you got a cast error. If you target only API > Honeycomb you can use the classes from android.content and android.app. Otherwise you will have to use the support library in your project (you will find information here: http://developer.android.com/tools/extras/support-library.html ) – Romain Guidoux Sep 12 '12 at 19:57
  • I see! I now introduced the support libraries. That brings me to yet another problem. The getLoaderManager returns a non support-library LoaderManager and Java off course immediately makes a big issue out of that. That brought me to this post: http://stackoverflow.com/questions/9918397/initializing-a-loader-in-an-activity which I followed. The stream of problems don't end here. The FragmentActivity does not offer a setListAdapter (off course). Again any help will really be appreciated! – Greforb Sep 13 '12 at 17:42
  • @Greforb You have found getSupportLoaderManager(). You can use it this way: http://stackoverflow.com/questions/7834647/how-to-get-loadermanager-in-a-listactivity If you do not currently use Fragments in your activity, I think you should update your application to use it. You can also use ActionBarSherlock (this is a compatibility library for using Action Bars on Android < Honeycomb). It lets you use SherlockListFragment, so you can call getLoaderManager() in onActivityCreated() – Romain Guidoux Sep 13 '12 at 19:00
  • Thank you so much. I'll try to use a ListFragment instead. – Greforb Sep 17 '12 at 18:25
1

Your SimpleCursorAdapter is tied to your Cursor, it's 'supplying' your adapter with the data fromt the Cursor. Think of your adapter as just that, an adapter, it lies on top of your Cursor and handles the delegation of data from your Cursor to your ListView. So, you should only close your Cursor when you aren't using your ListView anymore, fx. in onPause() (when the Activity isn't shown to the user anymore).

soren.qvist
  • 7,376
  • 14
  • 62
  • 91
0

I think the cursor continues to be used by the adapter, so you probably shouldn't close it right there.

You could use Activity.startManagingCursor() which will have the Activity take care of closing it for you when it's no longer used.

Jon O
  • 6,532
  • 1
  • 46
  • 57
  • But this method is deprecated right? Which is actually quite weird as to my knowledge there is no real and simple alternative if you use an SQLite database for data storage, you have to do various workarounds. With the LoaderManager/CursorLoader you have to use a ContentProvider, or am I completely wrong? – Greforb Sep 11 '12 at 19:56