title: SimpleCursorTreeAdapter and CursorLoader for ExpandableListView
body: <p>I am trying to asynchronously query a provider by using a <code>CursorLoader</code> with a <code>SimpleCursorTreeAdapter</code>     </p>
  
  <p>Here is my <code>Fragment</code> class which implements the <code>CursorLoader</code>    </p>
  
  <pre><code>public class GroupsListFragment extends ExpandableListFragment implements
    LoaderManager.LoaderCallbacks&lt;Cursor&gt; {
  
    private final String DEBUG_TAG = getClass().getSimpleName().toString();      
  
    private static final String[] CONTACTS_PROJECTION = new String[] {
      ContactsContract.Contacts._ID,
      ContactsContract.Contacts.DISPLAY_NAME };  
  
    private static final String[] GROUPS_SUMMARY_PROJECTION = new String[] {
      ContactsContract.Groups.TITLE, ContactsContract.Groups._ID,
      ContactsContract.Groups.SUMMARY_COUNT,
      ContactsContract.Groups.ACCOUNT_NAME,
      ContactsContract.Groups.ACCOUNT_TYPE,
      ContactsContract.Groups.DATA_SET };
  
    GroupsAdapter mAdapter;
  
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
      super.onActivityCreated(savedInstanceState);
  
      populateContactList();
  
      getLoaderManager().initLoader(-1, null, this);
    } 
  
    public Loader&lt;Cursor&gt; onCreateLoader(int id, Bundle args) {
      // This is called when a new Loader needs to be created.
      Log.d(DEBUG_TAG, "onCreateLoader for loader_id " + id);
      CursorLoader cl;
      if (id != -1) {
        // child cursor
        Uri contactsUri = ContactsContract.Data.CONTENT_URI;
        String selection = "(("
          + ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME
          + " NOTNULL) AND ("
          + ContactsContract.CommonDataKinds.GroupMembership.HAS_PHONE_NUMBER
          + "=1) AND ("
          + ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME
          + " != '') AND ("
          + ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID
          + " = ? ))";
        String sortOrder = ContactsContract.CommonDataKinds.GroupMembership.DISPLAY_NAME
          + " COLLATE LOCALIZED ASC";
        String[] selectionArgs = new String[] { String.valueOf(id) };
  
        cl = new CursorLoader(getActivity(), contactsUri,
          CONTACTS_PROJECTION, selection, selectionArgs, sortOrder);
      } else {
        // group cursor
        Uri groupsUri = ContactsContract.Groups.CONTENT_SUMMARY_URI;
        String selection = "((" + ContactsContract.Groups.TITLE
          + " NOTNULL) AND (" + ContactsContract.Groups.TITLE
          + " != '' ))";
        String sortOrder = ContactsContract.Groups.TITLE
          + " COLLATE LOCALIZED ASC";
        cl = new CursorLoader(getActivity(), groupsUri,
          GROUPS_SUMMARY_PROJECTION, selection, null, sortOrder);
      }
  
      return cl;
    }
  
    public void onLoadFinished(Loader&lt;Cursor&gt; loader, Cursor data) {
      // Swap the new cursor in. 
      int id = loader.getId();
      Log.d(DEBUG_TAG, "onLoadFinished() for loader_id " + id);
      if (id != -1) {
        // child cursor
        if (!data.isClosed()) {
          Log.d(DEBUG_TAG, "data.getCount() " + data.getCount());
          try {
            mAdapter.setChildrenCursor(id, data);
          } catch (NullPointerException e) {
            Log.w("DEBUG","Adapter expired, try again on the next query: "
              + e.getMessage());
          }
        }
      } else {
        mAdapter.setGroupCursor(data);
      }
  
    }
  
    public void onLoaderReset(Loader&lt;Cursor&gt; loader) {
      // This is called when the last Cursor provided to onLoadFinished()
      // is about to be closed.
      int id = loader.getId();
      Log.d(DEBUG_TAG, "onLoaderReset() for loader_id " + id);
      if (id != -1) {
        // child cursor
        try {
          mAdapter.setChildrenCursor(id, null);
        } catch (NullPointerException e) {
          Log.w("TAG", "Adapter expired, try again on the next query: "
            + e.getMessage());
        }
      } else {
        mAdapter.setGroupCursor(null);
      }
    }
  
    /**
    * Populate the contact list
    */
    private void populateContactList() {
      // Set up our adapter
      mAdapter = new GroupsAdapter(getActivity(),this,
        android.R.layout.simple_expandable_list_item_1,
        android.R.layout.simple_expandable_list_item_1,
        new String[] { ContactsContract.Groups.TITLE }, // Name for group layouts
        new int[] { android.R.id.text1 },
        new String[] { ContactsContract.Contacts.DISPLAY_NAME }, // Name for child layouts
        new int[] { android.R.id.text1 });
  
      setListAdapter(mAdapter);
    }
  }
  </code></pre>
  
  <p>And here is my adapter which subclasses <code>SimpleCursorTreeAdapter</code>     </p>
  
  <pre><code>public class GroupsAdapter extends SimpleCursorTreeAdapter {
  
    private final String DEBUG_TAG = getClass().getSimpleName().toString();
  
    private ContactManager mActivity;
    private GroupsListFragment mFragment;
  
    // Note that the constructor does not take a Cursor. This is done to avoid
    // querying the database on the main thread.
    public GroupsAdapter(Context context, GroupsListFragment glf,
      int groupLayout, int childLayout, String[] groupFrom,
      int[] groupTo, String[] childrenFrom, int[] childrenTo) {
  
      super(context, null, groupLayout, groupFrom, groupTo, childLayout,
        childrenFrom, childrenTo);
      mActivity = (ContactManager) context;
      mFragment = glf;
    }
  
    @Override
    protected Cursor getChildrenCursor(Cursor groupCursor) {
      // Given the group, we return a cursor for all the children within that group
      int groupId = groupCursor.getInt(groupCursor
        .getColumnIndex(ContactsContract.Groups._ID));
  
      Log.d(DEBUG_TAG, "getChildrenCursor() for groupId " + groupId);
  
      Loader loader = mActivity.getLoaderManager().getLoader(groupId); 
      if ( loader != null &amp;&amp; loader.isReset() ) { 
        mActivity.getLoaderManager().restartLoader(groupId, null, mFragment); 
      } else { 
        mActivity.getLoaderManager().initLoader(groupId, null, mFragment); 
      } 
  
    }
  
  }
  </code></pre>
  
  <p>The problem is that when i click one of the parent groups one of three things happens in what appears to be a inconsistent fashion.  </p>
  
  <p>1) Either the group opens up and the children appear below it</p>
  
  <p>2) The group does not open and the <code>setChildrenCursor()</code> call throws an <code>NullPointerException</code> error which gets caught in the try catch block</p>
  
  <p>3) The group does not open and no error is thrown</p>
  
  <p>Here is some debugging output in a scenario in which a group is expanded and showing the children:</p>
  
  <p>When all groups are displayed it ouputs:</p>
  
  <pre><code>05-20 10:08:22.765: D/GroupsListFragment(22132): onCreateLoader for loader_id -1
  05-20 10:08:23.613: D/GroupsListFragment(22132): onLoadFinished() for loader_id -1
  </code></pre>
  
  <p>-1 is the loader_id of the group cursor</p>
  
  <p>Then if i select one group in particular (let's just call it group A) it outputs:</p>
  
  <pre><code>05-20 23:22:31.140: D/GroupsAdapter(13844): getChildrenCursor() for groupId 67
  05-20 23:22:31.140: D/GroupsListFragment(13844): onCreateLoader for loader_id 67
  05-20 23:22:31.254: D/GroupsListFragment(13844): onLoadFinished() for loader_id 67
  05-20 23:22:31.254: D/GroupsListFragment(13844): data.getCount() 4
  05-20 23:22:31.254: W/GroupsListFragment(13844): Adapter expired, try again on the next query: null
  </code></pre>
  
  <p>The group does not expand and the <code>NullPointerException</code> is caught.  Then if i select another group (let's just call it group B) it outputs:</p>
  
  <pre><code>05-20 23:25:38.089: D/GroupsAdapter(13844): getChildrenCursor() for groupId 3
  05-20 23:25:38.089: D/GroupsListFragment(13844): onCreateLoader for loader_id 3
  05-20 23:25:38.207: D/GroupsListFragment(13844): onLoadFinished() for loader_id 3
  05-20 23:25:38.207: D/GroupsListFragment(13844): data.getCount() 6
  </code></pre>
  
  <p>This time, the <code>NullPointerException</code> is not thrown.  And instead of group B expanding, group A is expanded.</p>
  
  <p>Can anyone explain the behavior that the <code>setChildrenCursor()</code> call is exhibiting?</p>
  
  <p>I am thinking there is a problem with how the group/child CursorLoaders are instantiated in <code>onCreateLoader()</code>. For the group <code>CursorLoader</code> i just want all groups in my phone.  The child <code>CursorLoader</code> should contain all contacts within a group.  Does anyone have any ideas what could be the issue?</p>
  
  <p><strong>UPDATE</strong></p>
  
  <p>Thanks to @Yam's advice I have now modified the <code>getChildrenCursor()</code> method.  I am now selecting the groupCursor position not the value of ContactsContract.Groups._ID to pass into the initLoader() call.  I also changed the logic to call restartLoader() only when loader is not null and loader isReset is false.</p>
  
  <pre><code>protected Cursor getChildrenCursor(Cursor groupCursor) {
    // Given the group, we return a cursor for all the children within that
    // group
    int groupPos = groupCursor.getPosition();
    Log.d(DEBUG_TAG, "getChildrenCursor() for groupPos " + groupPos);
  
    Loader loader = mActivity.getLoaderManager().getLoader(groupPos);
    if (loader != null &amp;&amp; !loader.isReset()) {
      mActivity.getLoaderManager().restartLoader(groupPos, null, mFragment);
    } else {
      mActivity.getLoaderManager().initLoader(groupPos, null, mFragment);
    }
  
    return null;
  }
  </code></pre>
  
  <p>This definitely makes more sense and does not exhibit some of the erratic behavior of a group expanding sometimes and not other times.  </p>
  
  <p>However, there are contacts that are being displayed under a group that they don't belong to.  And also some groups that do have contacts in them but it won't show any contacts.  So it seems that the <code>getChildrenCursor()</code> issues may now be resolved.  </p>
  
  <p>But now it looks to be an issue of how the CursorLoaders are instantiated in the <code>onCreateLoader()</code> method.  Is the <code>CursorLoader</code> returned in the <code>onCreateLoader()</code> method for the child cursor being instantiated improperly?   </p>
  
  <p><strong>UPDATE</strong></p>
  
  <p>So I have identified one of my issues.  In the <code>getChildrenCursor()</code> method if I pass the groupId into the <code>initLoader()</code> method then in the <code>onCreateLoader()</code> method, when the <code>CursorLoader</code> is created it will get the correct groupid parameter for the query.  However, in the <code>onLoadFinished()</code> the call to <code>setChildrenCursor()</code> is getting passed the loader id for the first parameter not the groupPosition.  I'm guessing i have to map loader ids to group positions in some data structure.  But i'm not sure if this is the best approach.  Does anyone have any suggestions?</p>