title: SherlockFragmentActivity with multiple ListFragments and SQLite cursor battles
body: <p>I am using an Activity that extends <strong>SherlockFragmentActivity</strong>, that has 3 tabs. The 3 tabs are <strong>ListFragments</strong> that implement <strong>LoaderManager.LoaderCallbacks</strong>. The OnCreate method for the Activity loads the tabs like so</p>
  
  <pre><code>bar.addTab(bar.newTab()
      .setTag("venues_list")
      .setText(getString(R.string.list_venues_header))
      .setTabListener(new TabListener&lt;VenueListFragment&gt;(
          this, getString(R.string.list_invites_header), VenueListFragment.class, null)));
  
  // I do the EXACT same thing for the other two tabs, using their respective ListFragments
  
  if (savedInstanceState != null) {
      bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
  }
  </code></pre>
  
  <p>The layout that I'm loading the tabs into is very simple:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:orientation="vertical" &gt;
  
  &lt;/LinearLayout&gt;
  </code></pre>
  
  <p>Each of the tabs have a class that is identical to this, just called something different:</p>
  
  <pre><code>import android.content.Context;
  import android.content.SharedPreferences;
  import android.database.Cursor;
  import android.os.Bundle;
  import android.support.v4.app.LoaderManager;
  import android.support.v4.content.Loader;
  import android.support.v4.widget.CursorAdapter;
  import android.support.v4.widget.SimpleCursorAdapter;
  import android.view.View;
  import android.widget.AdapterView;
  import android.widget.AdapterView.OnItemClickListener;
  import android.widget.ListView;
  import android.widget.Toast;
  
  import com.actionbarsherlock.app.SherlockListFragment;
  import com.lateral.oursvp.R;
  import com.lateral.oursvp.database.SimpleCursorLoader;
  import com.lateral.oursvp.database.VenuesDataSource;
  
  /**
   * @author rabbott
   *
   */
  public class VenueListFragment extends SherlockListFragment implements LoaderManager.LoaderCallbacks&lt;Cursor&gt; {  
      SharedPreferences appPreferences;
  
      private CursorAdapter cursorAdapter;
  
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
      }
  
      @Override
      public void onActivityCreated(Bundle savedInstanceState) {
          super.onActivityCreated(savedInstanceState);
  
          // bind the columns of the cursor to the list
          String[] from = new String[] { VenuesDataSource.KEY_NAME, VenuesDataSource.KEY_DESCRIPTION };
          int[] to = new int[] { R.id.list_item_title, R.id.list_item_subtitle };
  
          cursorAdapter = new SimpleCursorAdapter( getActivity(), R.layout.list_item, null, from, to, 0);
  
          // retrieve the listview to populate
          ListView lv = (ListView) getActivity().findViewById(android.R.id.list);
  
          // set the adapter on the listview
          lv.setAdapter(cursorAdapter);
  
          // click event for each row of the list
          lv.setOnItemClickListener(new OnItemClickListener() {
  
              public void onItemClick(AdapterView&lt;?&gt; arg0, View view,
                      int position, long id) {
                  Cursor cursor = cursorAdapter.getCursor();
                  cursor.moveToPosition(position);
  
                  Toast.makeText(getActivity(), "Tapped row " + position + "!", Toast.LENGTH_SHORT).show();
              }
          });
  
          // Start out with a progress indicator.
          setListShown(false);
  
          // load the data
          getActivity().getSupportLoaderManager().initLoader(0, null, this);
      }
  
      public Loader&lt;Cursor&gt; onCreateLoader(int loaderId, Bundle args) {
          return new VenueCursorLoader(getActivity());
      }
  
      public void onLoadFinished(Loader&lt;Cursor&gt; loader, Cursor cursor) {
          cursorAdapter.swapCursor(cursor);
  
          // the list should now be shown
          if (isResumed()) {
              setListShown(true);
          } else {
              setListShownNoAnimation(true);
          }
      }
  
      public void onLoaderReset(Loader&lt;Cursor&gt; loader) {
          cursorAdapter.swapCursor(null);
      }
  
      public static final class VenueCursorLoader extends SimpleCursorLoader {
  
          Context mContext;
  
          public VenueCursorLoader(Context context) {
              super(context);
  
              mContext = context;
          }
  
          @Override
          public Cursor loadInBackground() {
              VenuesDataSource datasource = new VenuesDataSource(mContext);
  
              return datasource.getAllVenues(((EventActivity) mContext).getEventId());
          }
  
      }
  }
  </code></pre>
  
  <p>which uses the <strong>SimpleCursorLoaded</strong> defined here:</p>
  
  <pre><code>import android.content.Context;
  import android.database.Cursor;
  import android.support.v4.content.AsyncTaskLoader;
  
  public abstract class SimpleCursorLoader extends AsyncTaskLoader&lt;Cursor&gt; {
      private Cursor mCursor;
  
      public SimpleCursorLoader(Context context) {
          super(context);
      }
  
      /* Runs on a worker thread */
      @Override
      public abstract Cursor loadInBackground();
  
      /* Runs on the UI thread */
      @Override
      public void deliverResult(Cursor cursor) {
          if (isReset()) {
              // An async query came in while the loader is stopped
              if (cursor != null) {
                  cursor.close();
              }
              return;
          }
          Cursor oldCursor = mCursor;
          mCursor = cursor;
  
          if (isStarted()) {
              super.deliverResult(cursor);
          }
  
          if (oldCursor != null &amp;&amp; oldCursor != cursor &amp;&amp; !oldCursor.isClosed()) {
              oldCursor.close();
          }
      }
  
      /**
       * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
       * will be called on the UI thread. If a previous load has been completed and is still valid
       * the result may be passed to the callbacks immediately.
       * &lt;p/&gt;
       * Must be called from the UI thread
       */
      @Override
      protected void onStartLoading() {
          if (mCursor != null) {
              deliverResult(mCursor);
          }
          if (takeContentChanged() || mCursor == null) {
              forceLoad();
          }
      }
  
      /**
       * Must be called from the UI thread
       */
      @Override
      protected void onStopLoading() {
          // Attempt to cancel the current load task if possible.
          cancelLoad();
      }
  
      @Override
      public void onCanceled(Cursor cursor) {
          if (cursor != null &amp;&amp; !cursor.isClosed()) {
              cursor.close();
          }
      }
  
      @Override
      protected void onReset() {
          super.onReset();
  
          // Ensure the loader is stopped
          onStopLoading();
  
          if (mCursor != null &amp;&amp; !mCursor.isClosed()) {
              mCursor.close();
          }
          mCursor = null;
      }
  }
  </code></pre>
  
  <p>The issue I'm having is that when I select say.. the second tab (third tab does the same) it attempts to load the cursor, but when it tried to populate the list_items, I get an error saying </p>
  
  <pre><code>E/AndroidRuntime(2055): java.lang.IllegalArgumentException: column 'GIVEN_VARIABLE' does not exist
  </code></pre>
  
  <p>After setting a few break points I have discovered that the cursor its attempting to use is the cursor from the first tab, which loaded fine on inflation, but apparently isn't getting closed, and is apparently trying to be used again even though I (think) am sending it a different curser from getAllVenues()</p>
  
  <p><strong>EDIT: This is the last place the stack trace sends me</strong>
  When I set some breakpoints I can see that the cursor being provided here, is the cursor from the first tab, not that of the Venue tab..</p>
  
  <p><img src="https://i.stack.imgur.com/YM7gZ.png" alt="enter image description here"></p>
  
  <p><strong>EDIT: VenuesDataSource code to show the getAllVenues method</strong></p>
  
  <pre><code>import org.json.JSONArray;
  import org.json.JSONException;
  import org.json.JSONObject;
  
  import android.content.ContentValues;
  import android.content.Context;
  import android.database.Cursor;
  import android.database.SQLException;
  import android.util.Log;
  
  /**
   * @author rabbott
   *
   */
  public class VenuesDataSource extends appSQLiteHelper {
  
      public static final String TABLE_NAME               = "venues";
  
      // venue columns
      public static final String KEY_NAME                 = "name";
      public static final String KEY_DESCRIPTION          = "description";
      public static final String KEY_START_TIME           = "start_time";
      public static final String KEY_ADDRESS              = "address";
      public static final String KEY_CITY                 = "city";
      public static final String KEY_STATE                = "state";
      public static final String KEY_ZIP                  = "postal_code";
      public static final String KEY_LNG                  = "lng";
      public static final String KEY_LAT                  = "lat";
      public static final String KEY_PHONE                = "phone";
  
      public static String COLUMNS_VENUES[] = { 
          DatabaseConstants.KEY_ROWID, 
          DatabaseConstants.KEY_EVENT_ID,
          KEY_NAME, 
          KEY_DESCRIPTION, 
          KEY_START_TIME,
          KEY_ADDRESS, 
          KEY_CITY, 
          KEY_STATE,
          KEY_ZIP, 
          KEY_LNG, 
          KEY_LAT, 
          KEY_PHONE,
          DatabaseConstants.KEY_CREATED_AT
      };
  
      public static final String CREATE_STATEMENT = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
              + "_id              INTEGER PRIMARY KEY AUTOINCREMENT,"
              + "event_id         INTEGER NOT NULL,"
              + "name             TEXT NOT NULL,"
              + "description      TEXT,"
              + "start_time       TEXT,"
              + "address          TEXT NOT NULL,"
              + "city             TEXT NOT NULL,"
              + "state            TEXT NOT NULL,"
              + "postal_code      TEXT,"
              + "lat              TEXT,"
              + "lng              TEXT,"
              + "phone            TEXT,"
              + "created_at       TEXT);";
  
      public VenuesDataSource(Context context) {
          super(context);
  
          Log.i("VenueDataSource", "Constructor");
      }
  
      // create a new contact locally
      public long createVenue(Integer venue_id, Integer event_id, String name, String description, String start_time, String address, String city, String state, String postal_code, String lat, String lng, String phone, String created_at) {
          ContentValues initialValues = new ContentValues();
  
          initialValues.put(DatabaseConstants.KEY_ROWID, event_id);
          initialValues.put(DatabaseConstants.KEY_EVENT_ID, event_id);
          initialValues.put(KEY_NAME, name);
          initialValues.put(KEY_DESCRIPTION, description);
          initialValues.put(KEY_START_TIME, start_time);
          initialValues.put(KEY_ADDRESS, address);
          initialValues.put(KEY_CITY, city);
          initialValues.put(KEY_STATE, state);
          initialValues.put(KEY_ZIP, postal_code);
          initialValues.put(KEY_PHONE, phone);
          initialValues.put(KEY_LAT, lat);
          initialValues.put(KEY_LNG, lng);
          initialValues.put(DatabaseConstants.KEY_CREATED_AT, created_at);
  
          return getWritableDatabase().insert(TABLE_NAME, null, initialValues);
      }
  
      // retrieve a venue from the local database
      public Cursor getVenue(long rowId) throws SQLException {
          Cursor mCursor = getWritableDatabase().query(true, TABLE_NAME, COLUMNS_VENUES, DatabaseConstants.KEY_ROWID + "=" + rowId, null, null, null, null, null);
  
          if (mCursor != null) {
              mCursor.moveToFirst();
          }
  
          return mCursor;
      }
  
      // update a local venue
      public long updateVenue(Integer venue_id, Integer event_id, String name, String description, String start_time, String address, String city, String state, String postal_code, String lat, String lng, String phone) {
          ContentValues initialValues = new ContentValues();
  
          initialValues.put(DatabaseConstants.KEY_EVENT_ID, event_id);
          initialValues.put(KEY_NAME, name);
          initialValues.put(KEY_DESCRIPTION, description);
          initialValues.put(KEY_START_TIME, start_time);
          initialValues.put(KEY_ADDRESS, address);
          initialValues.put(KEY_CITY, city);
          initialValues.put(KEY_STATE, state);
          initialValues.put(KEY_ZIP, postal_code);
          initialValues.put(KEY_PHONE, phone);
          initialValues.put(KEY_LAT, lat);
          initialValues.put(KEY_LNG, lng);
  
          return getWritableDatabase().update(TABLE_NAME, initialValues, "_id=?", new String[] { Long.toString(venue_id) });
      }
  
      // delete a local venue
      public boolean deleteVenue(long rowId) {
          return (getWritableDatabase().delete(TABLE_NAME, DatabaseConstants.KEY_ROWID + "=" + rowId, null) &gt; 0);
      }
  
      // retrieve all local venues
      public Cursor getAllVenues(long event_id) {
          Cursor mCursor = getWritableDatabase().query(true, TABLE_NAME, COLUMNS_VENUES, DatabaseConstants.KEY_EVENT_ID + "=" + event_id, 
                  null, null, null, null, null);
  
          return mCursor; 
      }
  
      public boolean venueExists(int venue_id) {
          Cursor mCursor = getWritableDatabase().query(true, TABLE_NAME, COLUMNS_VENUES, DatabaseConstants.KEY_ROWID + "=" + venue_id,
                      null, null, null, null, null);
  
          if (mCursor.getCount() == 0) {
              mCursor.close();
              return false;
          } else {
              mCursor.close();
              return true;
          }
      }
  
      public void parseVenue(JSONObject venueJson) throws JSONException {
          boolean venue_exists = false;
  
          int event_id, venue_id;
          String venue_name, venue_description, start_time, venue_address, venue_city, venue_state, venue_postal_code, lat, lng, venue_phone, created_at;
  
          venue_id            = venueJson.getInt(DatabaseConstants.KEY_REMOTE_ID);
          event_id            = venueJson.getInt(DatabaseConstants.KEY_EVENT_ID);
          venue_name          = venueJson.getString(KEY_NAME);
          venue_description   = venueJson.getString(KEY_DESCRIPTION);
          start_time          = venueJson.getString(KEY_START_TIME);
          venue_address       = venueJson.getString(KEY_ADDRESS);
          venue_city          = venueJson.getString(KEY_CITY);
          venue_state         = venueJson.getString(KEY_STATE);
          venue_postal_code   = venueJson.getString(KEY_ZIP);
          venue_phone         = venueJson.getString(KEY_PHONE);
          lat                 = venueJson.getString(KEY_LAT);
          lng                 = venueJson.getString(KEY_LNG);
          created_at          = venueJson.getString(DatabaseConstants.KEY_CREATED_AT);
  
          // check to see if this venue already exists
          venue_exists = this.venueExists(venue_id);
  
          if (venue_exists == true) {
              this.updateVenue(
                      venue_id, 
                      event_id, 
                      venue_name,
                      venue_description,
                      start_time,
                      venue_address, 
                      venue_city, 
                      venue_state,
                      venue_postal_code, 
                      lat,
                      lng, 
                      venue_phone);
          } else {
              this.createVenue(
                      venue_id,
                      event_id,
                      venue_name,
                      venue_description,
                      start_time,
                      venue_address, 
                      venue_city,
                      venue_state,
                      venue_postal_code, 
                      lat,
                      lng,
                      venue_phone,
                      created_at);
          }
      }
  
      public void parseVenues(JSONArray venuesArray) throws JSONException {
          JSONObject venueJson;
  
          for (int i = 0; i &lt; venuesArray.length(); i++) {
              // Iterate through each venue
              venueJson = venuesArray.getJSONObject(i);
  
              this.parseVenue(venueJson);
          }
      }
  }
  </code></pre>