estoy tratando de consultar de forma asincrónica un proveedor mediante el uso de un CursorLoader
con un SimpleCursorTreeAdapter
SimpleCursorTreeAdapter y CursorLoader para ExpandableListView
Aquí está mi clase Fragment
que implementa el 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);
}
}
Y aquí es mi adaptador que subclases 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);
}
}
}
The El problema es que cuando hago clic en uno de los grupos principales, una de las tres cosas sucede en lo que parece ser una moda incoherente.
1) O bien el grupo se abre y los niños aparecen debajo de ella
2) El grupo no se abre y la llamada setChildrenCursor()
lanza un error NullPointerException
la que se ve atrapado en el bloque catch try
3) el grupo no se abre y no hay error se lanza
Aquí hay alguna información de depuración en un escenario en el que se expande un grupo y mostrando a los niños:
Cuando todos los grupos están disp layed que SALIDAS:
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 grupo
Entonces, si selecciono un grupo en particular (vamos a llamarlo el grupo A) emite:
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 se captura el NullPointerException
. Entonces, si selecciono otro grupo (vamos a llamarlo grupo B) que da salida:
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, el NullPointerException
no se lanza. Y en lugar de que el grupo B se expanda, el grupo A se expande.
¿Alguien puede explicar el comportamiento que está mostrando la llamada setChildrenCursor()
?
Creo que hay un problema con la creación de instancias de CursorLoaders grupales/secundarios en onCreateLoader()
. Para el grupo CursorLoader
solo quiero todos los grupos en mi teléfono. El hijo CursorLoader
debe contener todos los contactos dentro de un grupo. ¿Alguien tiene alguna idea de lo que podría ser el problema?
ACTUALIZACIÓN
Gracias a @ consejo de Yam Ahora he modificado el método getChildrenCursor()
. Ahora estoy seleccionando la posición del grupoCursor 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 presenta algunos de los comportamientos erráticos de un grupo en expansión veces y no otras veces.
Sin embargo, hay contactos que se muestran debajo de un grupo al que no pertenecen. Y también algunos grupos que sí tienen contactos pero no muestran ningún contacto. Parece que los problemas de getChildrenCursor()
ahora pueden resolverse.
Pero ahora parece ser un problema de cómo se crean instancias de CursorLoaders en el método onCreateLoader()
. ¿Se devuelve el CursorLoader
en el método onCreateLoader()
para que el cursor secundario se cree una instancia incorrecta?
ACTUALIZACIÓN
Así que han identificado a uno de mis problemas. En el método getChildrenCursor()
si paso el ID de grupo en el método initLoader()
, en el método onCreateLoader()
, cuando se crea el CursorLoader
, obtendrá el parámetro groupid correcto para la consulta. Sin embargo, en el onLoadFinished()
, la llamada al setChildrenCursor()
se está transfiriendo a la id del cargador para el primer parámetro, no a la posición de grupo. Supongo que tengo que mapear identificadores de cargadores para agrupar posiciones en alguna estructura de datos. Pero no estoy seguro de si este es el mejor enfoque. ¿Alguien tiene alguna sugerencia?
que acabo de hacer esto, pero no estaba usando un CursorLoader, por lo que me está tirando ...En mi implementación, getChildrenCursor devuelve un cursor. Con el gestor de carga, ¿a dónde va realmente ese cursor/datos? Si no está introduciendo un cursor en el constructor, ¿qué se está introduciendo en 'getChildrenCursor' como groupCursor? – Barak
groupCursor se ha establecido en el método onLoadFinished() del LoaderManager. He recorrido el código con un depurador y en el método getChildenCursor(), groupCursor siempre está definido. – toobsco42
No sé, realmente suena como que no siempre obtienes un niño pobladoCursor ... – Barak