SimpleCursorTreeAdapter i CursorLoader dla ExpandableListView

Próbuję asynchronicznie zapytać dostawcę za pomocą aCursorLoader zSimpleCursorTreeAdapter

Tutaj jest mójFragment klasa, która implementujeCursorLoader

public class GroupsListFragment extends ExpandableListFragment implements
  LoaderManager.LoaderCallbacks<Cursor> {

  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<Cursor> 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<Cursor> 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<Cursor> 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);
  }
}

A oto mój adapter, którego podklasySimpleCursorTreeAdapter

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 && loader.isReset() ) { 
      mActivity.getLoaderManager().restartLoader(groupId, null, mFragment); 
    } else { 
      mActivity.getLoaderManager().initLoader(groupId, null, mFragment); 
    } 

  }

}

Problem polega na tym, że gdy klikam jedną z grup nadrzędnych, dzieje się jedna z trzech rzeczy, które wydają się niespójne.

1) Grupa otwiera się, a dzieci pojawiają się pod nią

2) Grupa nie otwiera się i grupasetChildrenCursor() połączenie rzucaNullPointerException błąd, który zostaje złapany w bloku try catch

3) Grupa nie otwiera się i nie jest zgłaszany błąd

Oto niektóre wyniki debugowania w scenariuszu, w którym grupa jest rozwijana i pokazuje dzieci:

Po wyświetleniu wszystkich grup można je wyświetlić:

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

-1 to id_ ładowacza kursora grupy

Następnie, jeśli wybiorę w szczególności jedną grupę (nazwijmy ją po prostu grupą A), wysyła:

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

Grupa nie rozwija się iNullPointerException Został złapany. Następnie, jeśli wybiorę inną grupę (nazwijmy ją po prostu grupą B), wysyła:

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

Tym razemNullPointerException nie jest rzucany. I zamiast rozszerzania grupy B, grupa A jest rozwijana.

Czy ktoś może wyjaśnić zachowanie, któresetChildrenCursor() dzwoni?

Myślę, że istnieje problem z tworzeniem instancji CursorLoaders grupy / potomkaonCreateLoader(). Dla grupyCursorLoader Chcę tylko wszystkie grupy w moim telefonie. DzieckoCursorLoader powinien zawierać wszystkie kontakty w grupie. Czy ktoś ma jakieś pomysły, co może być problemem?

AKTUALIZACJA

Dzięki radom @ Yam zmodyfikowałem terazgetChildrenCursor() metoda. Teraz wybieram pozycję groupCursor, a nie wartość ContactsContract.Groups._ID, aby przejść do wywołania initLoader (). Zmieniłem też logikę wywołującą restartLoader () tylko wtedy, gdy program ładujący nie jest pusty, a program ładujący isReset jest fałszywy.

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 && !loader.isReset()) {
    mActivity.getLoaderManager().restartLoader(groupPos, null, mFragment);
  } else {
    mActivity.getLoaderManager().initLoader(groupPos, null, mFragment);
  }

  return null;
}

To z pewnością ma więcej sensu i nie wykazuje pewnych nieregularnych zachowań grupy rozwijającej się czasami, a nie innych razy.

Istnieją jednak kontakty, które są wyświetlane pod grupą, do której nie należą. A także niektóre grupy, które mają w sobie kontakty, ale nie pokażą żadnych kontaktów. Wygląda więc na to, żegetChildrenCursor() problemy mogą teraz zostać rozwiązane.

Ale teraz wygląda na to, że CursorLoaders są tworzone w instancjionCreateLoader() metoda. JestCursorLoader wrócił wonCreateLoader() metoda niepoprawnego tworzenia kursora podrzędnego?

AKTUALIZACJA

Zidentyfikowałem więc jeden z moich problemów. wgetChildrenCursor() metoda, jeśli przekażę groupId doinitLoader() metoda następnie wonCreateLoader() metoda, gdyCursorLoader zostanie utworzony, otrzyma poprawny parametr groupid dla zapytania. Jednak wonLoadFinished() wezwanie dosetChildrenCursor() przekazuje id modułu ładującego dla pierwszego parametru, a nie groupPosition. Zgaduję, że muszę mapować identyfikatory programu ładującego do grupowania pozycji w jakiejś strukturze danych. Ale nie jestem pewien, czy jest to najlepsze podejście. Czy ktoś ma jakieś sugestie?

questionAnswers(3)

yourAnswerToTheQuestion