Search code examples
androidandroid-fragmentsactionbarsherlocksimplecursoradapterandroid-loadermanager

Android SherlockFragmentActivity with multiple ListFragments


I have an SherlockFragmentActivity that is showing a few tabs. Each tab is ListFragment.

Each ListFragment is getting created like so:

ActionBar bar = getSupportActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
bar.setDisplayHomeAsUpEnabled(true);
bar.setDisplayShowTitleEnabled(true);

// users event list
bar.addTab(bar.newTab()
    .setTag("contacts_list")
    .setText(getString(R.string.list_contacts_header))
    .setTabListener(new TabListener<ContactListFragment>(
        this, getString(R.string.list_events_header), ContactListFragment.class, null)));

Then, each of the ListFragments are loaded like so:

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

    // database cursor containing all venues for this event
    venuesCursor = getDatasource().getAllVenues(((EventActivity) getActivity()).getEventId());

    // hand the cursor to the system to manage
    getActivity().startManagingCursor(venuesCursor);  

    // 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, venuesCursor, from, to);

    // 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<?> 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);

    // prepare the loader -- either re-connect with an existing one, or start a new one.
    // getLoaderManager().initLoader(0, null, this);

    // load the data
    getActivity().getSupportLoaderManager().initLoader(0, null, this);
}

Like I said, there are multiple tabs on this activity in the form of ListFragments. The issue I'm having is that when clicking on the tabs to select them, I get

E/AndroidRuntime(2519): java.lang.IllegalArgumentException: column 'name' does not exist

Which is wrong, I've used adb to view the database, the columns that its complaining about are 100% there, so it has to be something to do with not closing a cursor or something and when the above loads its actually using the wrong cursor.

EDIT: Adding CursorLoader Code

public static final class VenueCursorLoader extends SimpleCursorLoader {

    Context mContext;

    public VenueCursorLoader(Context context) {
        super(context);

        mContext = context;
    }

    @Override
    public Cursor loadInBackground() {
        Cursor cursor = null;
        VenuesDataSource datasource = new VenuesDataSource(mContext);

        // TODO: provide the event_id to the getAllVenues method
        cursor = datasource.getAllVenues(((EventActivity) mContext).getEventId());

        if (cursor != null) {
            cursor.getCount();
        }

        return cursor;
    }

}

Any help much appreciated..


Solution

  • This question is essentially answered here

    Basically everything you need to understand is outlined in the answer. There are several fixes you should make:


    • You should pass the CursorAdapter a null cursor to begin with. The LoaderManager will have the CursorLoader to perform the initial query for you. (see my answer above). Also note that the constructor you are currently using is deprecated. You should use this one instead (pass it 0 as the flag).

      cursorAdapter = new SimpleCursorAdapter( getActivity(), R.layout.list_item, null, from, to, 0);


    • Remove this line:

      getActivity().startManagingCursor(venuesCursor);
      

      The whole point of the LoaderManager is that it manages the cursor for you. You don't need to "hand the cursor to the system to manage"... that is exactly the job of the LoaderManager. :)


    • Because of the reasons I described in #1 and #2, it looks like this line of code is unnecessary:

      venuesCursor = getDatasource().getAllVenues(
              ((EventActivity) getActivity()).getEventId());
      

    • I'm also not sure why you are overriding onItemClick. Since you are using ListFragments, I suspect you want to be overriding onListItemClick instead.

    • I'm not sure why you have included these lines, but it looks like you want to remove them as well.

      Cursor cursor = cursorAdapter.getCursor();
      cursor.moveToPosition(position);
      

      For the most part, you shouldn't manipulate the adapter's cursor, as it is managed by the internal system and is bound to the LoaderManager.