SimpleCursorTreeAdapter y CursorLoader para ExpandableListView
Estoy tratando de consultar de forma asíncrona a un proveedor utilizando unCursorLoader
con unSimpleCursorTreeAdapter
Aquí está miFragment
clase que implementa laCursorLoader
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);
}
}
Y aquí está mi adaptador que subclasesSimpleCursorTreeAdapter
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);
}
}
}
El problema es que cuando hago clic en uno de los grupos de padres, ocurre una de tres cosas en lo que parece ser una forma inconsistente.
1) El grupo se abre y los niños aparecen debajo.
2) El grupo no se abre y elsetChildrenCursor()
llamada lanza unNullPointerException
error que queda atrapado en el bloque try catch
3) El grupo no se abre y no se produce ningún error.
Aquí hay algunos resultados de depuración en un escenario en el que un grupo se expande y muestra a los hijos:
Cuando todos los grupos se muestran, sale:
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 es el loader_id del cursor del grupo
Luego, si selecciono un grupo en particular (llamémoslo grupo A), genera:
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
El grupo no se expande y laNullPointerException
está atrapado. Luego, si selecciono otro grupo (llamémoslo grupo B), genera:
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
Esta vez, elNullPointerException
no se tira Y en lugar de la expansión del grupo B, el grupo A se expande.
¿Alguien puede explicar el comportamiento que elsetChildrenCursor()
¿La convocatoria está exhibiendo?
Estoy pensando que hay un problema con la forma en que los CursorLoaders de grupo / niño son instanciados enonCreateLoader()
. Para el grupoCursorLoader
Sólo quiero todos los grupos en mi teléfono. El niñoCursorLoader
Debe contener todos los contactos dentro de un grupo. ¿Alguien tiene alguna idea de cuál podría ser el problema?
ACTUALIZAR
Gracias al consejo de @Yam ahora he modificado elgetChildrenCursor()
método. Ahora estoy seleccionando la posición de groupCursor y no el valor de ContactsContract.Groups._ID para pasar a la llamada initLoader (). También cambié la lógica para llamar a restartLoader () solo cuando el cargador no es nulo y el cargador isReset es falso.
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;
}
Esto definitivamente tiene más sentido y no muestra parte del comportamiento errático de un grupo que se expande a veces y no a otras.
Sin embargo, hay contactos que se muestran en un grupo al que no pertenecen. Y también algunos grupos que sí tienen contactos, pero no mostrará ningún contacto. Así que parece que lagetChildrenCursor()
Los problemas ahora pueden ser resueltos.
Pero ahora parece ser un problema de cómo los CursorLoaders son instanciados en laonCreateLoader()
método. Es elCursorLoader
regresó en elonCreateLoader()
¿Método para que el cursor hijo no se ejecute correctamente?
ACTUALIZAR
Así que he identificado uno de mis problemas. En elgetChildrenCursor()
método si paso el groupId en elinitLoader()
método entonces en elonCreateLoader()
método, cuando elCursorLoader
se crea, obtendrá el parámetro groupid correcto para la consulta. Sin embargo, en elonLoadFinished()
la llamada asetChildrenCursor()
Se está pasando la identificación del cargador para el primer parámetro, no la posición de grupo. Supongo que tengo que asignar los identificadores del cargador a las posiciones de grupo en alguna estructura de datos. Pero no estoy seguro de si este es el mejor enfoque. ¿Alguien tiene alguna sugerencia?