Note that there are some explanatory texts on larger screens.

plurals
  1. POSimpleCursorTreeAdapter and CursorLoader for ExpandableListView
    primarykey
    data
    text
    <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>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload