22

Ich versuche asynchron einen Provider abzufragen, indem ein CursorLoader mit einem SimpleCursorTreeAdapterSimpleCursorTreeAdapter und CursorLoader für ExpandableListView

Hier verwenden ist meine Fragment Klasse, die die CursorLoader

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

implementiert Und hier ist mein Adapter, die Unterklassen SimpleCursorTreeAdapter

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

    } 

} 

Die Problem ist, dass wenn ich eine der Elterngruppen klicken Sie auf eine der drei Dinge passiert, in eine scheinbar im Widerspruch Mode sein.

1) Entweder öffnet sich die Gruppe und die Kinder erscheinen darunter

2) Die Gruppe wird nicht geöffnet und die setChildrenCursor() Aufruf wirft einen NullPointerException Fehler, der in der try catch-Block verfängt

3) die Gruppe wird nicht geöffnet und kein Fehler

geworfen

Hier wird einige Debugging-Ausgabe in einem Szenario, in dem eine Gruppe erweitert wird und die Kinder zeigt:

Wenn alle Gruppen sind disp es ouputs gelegt:

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 die loader_id der Gruppe Cursor

Dann, wenn ich insbesondere eine Gruppe wählen (nennen wir es nur Gruppe A) gibt sie:

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 

Die Gruppe wird nicht erweitert und die NullPointerException wird abgefangen. Dann, wenn ich eine andere Gruppe auswählen (nennen wir es einfach Gruppe B) gibt sie:

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 

Dieses Mal wird die NullPointerException nicht geworfen. Und anstatt Gruppe B zu erweitern, wird Gruppe A erweitert.

Kann jemand das Verhalten erklären, dass der setChildrenCursor() Anruf zeigt, wird?

Ich denke es gibt ein Problem mit, wie die Gruppe/Kind CursorLoaders in onCreateLoader() instanziiert werden. Für die Gruppe CursorLoader Ich möchte nur alle Gruppen in meinem Handy. Das Kind CursorLoader sollte alle Kontakte innerhalb einer Gruppe enthalten. Hat jemand irgendwelche Ideen, was könnte das Problem sein?

UPDATE

Dank @ Yam Rat habe ich jetzt die getChildrenCursor() Verfahren modifiziert. Ich Auswahl jetzt die groupCursor Position nicht den Wert von ContactsContract.Groups._ID in die initLoader() Aufruf zu übergeben. Ich habe auch die Logik nur restartLoader() aufrufen, wenn Lader nicht null ist und Lader isReset ist falsch.

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

Dies macht definitiv mehr Sinn und zeigt nicht das erratische Verhalten einer Gruppe, die sich manchmal und nicht in anderen Zeiten ausdehnt.

Es gibt jedoch Kontakte, die unter einer Gruppe angezeigt werden, zu der sie nicht gehören. Und auch einige Gruppen, die Kontakte haben, aber keine Kontakte zeigen. So scheint es, dass die getChildrenCursor() Probleme jetzt gelöst werden können.

Aber jetzt scheint es ein Problem zu sein, wie die CursorLoaders in der onCreateLoader() Methode instanziiert werden. Wird CursorLoader in der onCreateLoader()-Methode für den Child-Cursor zurückgegeben, der nicht ordnungsgemäß instanziiert wird?

UPDATE

So habe ich eines meiner Probleme identifiziert. In der getChildrenCursor() Methode, wenn ich die GroupId in die initLoader() Methode dann in der onCreateLoader() Methode übergeben, wenn die CursorLoader erstellt wird, wird es den richtigen GroupID-Parameter für die Abfrage erhalten. In der onLoadFinished() wird jedoch der Aufruf an setChildrenCursor() die Loader-ID für den ersten Parameter nicht die GroupPosition übergeben. Ich denke, ich muss Loader IDs Gruppenpositionen in einigen Datenstruktur zuordnen. Aber ich bin mir nicht sicher, ob dies der beste Ansatz ist. Hat jemand irgendwelche Vorschläge?

+0

Ich habe gerade dies getan, aber es war nicht mit einem CursorLoader, so dass mich wirft ...In meiner Implementation gibt der getChildrenCursor einen Cursor zurück. Mit dem Lademanager, wo gehen die Cursor/Daten eigentlich hin? Wenn Sie keinen Cursor in den Konstruktor eingeben, was wird in 'getChildrenCursor' als groupCursor eingegeben? – Barak

+0

Der groupCursor wurde in der onLoadFinished() - Methode des LoaderManagers festgelegt. Ich bin mit einem Debugger durch den Code gegangen und in der Methode getChildenCursor() ist der groupCursor immer definiert. – toobsco42

+0

Ich weiß nicht, es klingt wirklich wie Sie nicht immer ein bevölkertes Kind bekommenCursor ... – Barak

Antwort

16

So habe ich herausgefunden, dass ich brauchte loaderids zu groupPositions abzubilden und das ist mein Problem gelöst:

Hier ist meine Fragment Klasse, die implementiert die CursorLoader

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_PROJECTION = new String[] { 
    ContactsContract.Groups.TITLE, ContactsContract.Groups._ID }; 

    GroupsAdapter mAdapter; 

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

    populateContactList(); 

    // Prepare the loader. Either re-connect with an existing one, 
    // or start a new one. 
    Loader loader = getLoaderManager().getLoader(-1); 
    if (loader != null && !loader.isReset()) { 
     getLoaderManager().restartLoader(-1, null, this); 
    } else { 
     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.Contacts.DISPLAY_NAME 
     + " NOTNULL) AND (" 
     + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND (" 
     + ContactsContract.Contacts.DISPLAY_NAME + " != '') AND (" 
     + ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID 
     + " = ?))"; 
     String sortOrder = ContactsContract.Contacts.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_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_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()); 

     HashMap<Integer,Integer> groupMap = mAdapter.getGroupMap(); 
     try { 
      int groupPos = groupMap.get(id); 
      Log.d(DEBUG_TAG, "onLoadFinished() for groupPos " + groupPos); 
      mAdapter.setChildrenCursor(groupPos, 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); 
    } 
} 

Und hier ist mein Adapter, der SimpleCursorTreeAdapter Unterklassen

+2

Hinweis: Android hat ein SparseIntArray (siehe http://developer.android.com/reference/android/util/SparseIntArray.html), das besser zu verwenden ist als HashMap und dient demselben Zweck. –

+0

Danke für die Empfehlung. Ich werde es versuchen. – toobsco42

+1

Nach dieser Analyse: http://mobile.dzone.com/articles/tweaking-your-android erreichen sie ähnliche Ergebnisse für die Anzahl der Elemente, die ich in der Hashmap habe, die weniger als 1.000 ist. – toobsco42

1

Ich habe schlechte Erfahrung mit ExpandableListView. Das Verhalten in verschiedenen Android-Versionen ist unterschiedlich. Wenn Sie nicht schon zu tief darin sind, möchten Sie möglicherweise Ihre Schnittstelle neu entwerfen.

Wie auch immer, auf Ihre Fragen schlage ich Ihnen vor, diese 3 Punkte zu überprüfen.

Erstens, um in Ihren Anruf den Cursor loader Kinder init

mActivity.getLoaderManager().initLoader(groupId, null, mFragment); 

Die groupId Sie bestanden ist der Wert von ContactsContract.Groups._ID. Dann verwenden Sie diese ID im ersten Parameter von setChildrenCursor. Das ist wahrscheinlich falsch. Anstatt die groupId in den initLoader zu übergeben, versuchen Sie, die Gruppencursorposition zu übergeben. Zum Beispiel:

int iGroupPos = groupCursor.getPosition(); 
if (loader != null && !loader.isReset()) 
    mActivity.getLoaderManager().restartLoader(iGroupPos, null, mFragment); 
else 
    mActivity.getLoaderManager().initLoader(iGroupPos, null, mFragment); 

Zweitens können Sie sehen, dass ich in den Code oben vorgeschlagen, sollten Sie wahrscheinlich restartLoader nur anrufen, wenn loader nicht null ist und Lader isReset ist falsch.

Drittens müssen Sie einen Wert für den getChildrenCursor-Aufruf zurückgeben, der vermutlich null sein sollte.

+0

Dies macht definitiv Sinn und scheint näher zu sein, was genau ich will. Aber ich habe immer noch Probleme, die ich unter UPDATE in der obigen Frage erwähnt habe. – toobsco42

2

In meinem Fall verwende ich das erste Argument von initLoader (oder restartLoader) zu geben die Gruppenposition für Rückruf und Verwendung von Bundle, um Kinderdaten in getChildrenCursor abzurufen.

Wie folgt;

public class ExpandableListAdapter extends SimpleCursorTreeAdapter implements LoaderManager.LoaderCallbacks<Cursor> { 
    private Context mContext; 
    private LoaderManager mManager; 

    public ExpandableListAdapter(
      Context context, ExpandableListAdapterListener listener, LoaderManager manager, Cursor groupCursor, 
      int groupLayout, String[] groupFrom, int[] groupTo, 
      int childLayout, String[] childFrom, int[] childTo) { 
     super(context, groupCursor, groupLayout, groupFrom, groupTo, childLayout, childFrom, childTo); 
     mContext = context; 
     mManager = manager; 
    } 
    @Override 
    protected Cursor getChildrenCursor(Cursor groupCursor) { 
     final long idGroup = groupCursor.getLong(groupCursor.getColumnIndex("_id")); 
     Bundle bundle = new Bundle(); 
     bundle.putLong("idGroup", idGroup); 
     int groupPos = groupCursor.getPosition(); 
     if (mManager.getLoader(groupPos) != null && !mManager.getLoader(groupPos).isReset()) { 
      mManager.restartLoader(groupPos, bundle, this); 
     } 
     else { 
      mManager.initLoader(groupPos, bundle, this); 
     } 
     return null; 
    } 
    @Override 
    public Loader<Cursor> onCreateLoader(int groupPos, Bundle bundle) { 
     long idGroup = bundle.getLong("idGroup"); 
     return new CursorLoader(
       mContext, 
       Provider.URI, 
       new String[]{Table.ID, Table.ID_GROUP, Table.TITLE, Table.CONTEXT}, 
       Table.ID_GROUP + " = ?", 
       new String[]{String.valueOf(idGroup)}, 
       Table.CREATED + " DESC" 
     ); 
    } 
    @Override 
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { 
     setChildrenCursor(loader.getId(), cursor); 
    } 
    @Override 
    public void onLoaderReset(Loader<Cursor> loader) { 
    } 
} 
+1

Dies funktioniert nicht, wenn Sie den Lader neu starten müssen, da die Extras nur zur Ladezeit des Laders verwendet werden. –