¿Cuál es la mejor manera de iterar un cursor de Android?
Con frecuencia veo código que implica iterar sobre el resultado de una consulta de base de datos, hacer algo con cada fila y luego pasar a la siguiente fila. Los ejemplos típicos son los siguientes.
Cursor cursor = db.rawQuery(...);
cursor.moveToFirst();
while (cursor.isAfterLast() == false)
{
...
cursor.moveToNext();
}
Cursor cursor = db.rawQuery(...);
for (boolean hasItem = cursor.moveToFirst();
hasItem;
hasItem = cursor.moveToNext()) {
...
}
Cursor cursor = db.rawQuery(...);
if (cursor.moveToFirst()) {
do {
...
} while (cursor.moveToNext());
}
Todos estos me parecen excesivamente prolijos, cada uno con múltiples llamadas a Cursor
métodos. ¿Seguramente debe haber una manera más ordenada?
La forma más sencilla es esta:
while (cursor.moveToNext()) {
...
}
El cursor comienza antes de la primera fila de resultados, por lo que en la primera iteración se mueve al primer resultado, si existe . Si el cursor está vacío o la última fila ya ha sido procesada, entonces el bucle sale ordenadamente.
Por supuesto, no olvides cerrar el cursor una vez que hayas terminado, preferiblemente en una finally
cláusula.
Cursor cursor = db.rawQuery(...);
try {
while (cursor.moveToNext()) {
...
}
} finally {
cursor.close();
}
Si su objetivo es API 19+, puede utilizar try-with-resources.
try (Cursor cursor = db.rawQuery(...)) {
while (cursor.moveToNext()) {
...
}
}
La forma más atractiva que he encontrado para pasar por un cursor es la siguiente:
Cursor cursor;
... //fill the cursor here
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
// do what you need with the cursor here
}
No olvides cerrar el cursor después.
EDITAR: La solución proporcionada es excelente si alguna vez necesita iterar un cursor del que no es responsable. Un buen ejemplo sería si está tomando un cursor como argumento en un método y necesita escanear el cursor en busca de un valor determinado, sin tener que preocuparse por la posición actual del cursor.
Sólo me gustaría señalar una tercera alternativa que también funciona si el cursor no está en la posición inicial:
if (cursor.moveToFirst()) {
do {
// do what you need with the cursor here
} while (cursor.moveToNext());
}
A continuación podría ser la mejor manera:
if (cursor.moveToFirst()) {
while (!cursor.isAfterLast()) {
//your code to implement
cursor.moveToNext();
}
}
cursor.close();
El código anterior aseguraría que pasaría por toda la iteración y no escaparía de la primera ni de la última iteración.
¿Qué tal usar el bucle foreach?
Cursor cursor;
for (Cursor c : CursorUtils.iterate(cursor)) {
//c.doSth()
}
Sin embargo, mi versión de CursorUtils debería ser menos fea, pero cierra automáticamente el cursor:
public class CursorUtils {
public static Iterable<Cursor> iterate(Cursor cursor) {
return new IterableWithObject<Cursor>(cursor) {
@Override
public Iterator<Cursor> iterator() {
return new IteratorWithObject<Cursor>(t) {
@Override
public boolean hasNext() {
t.moveToNext();
if (t.isAfterLast()) {
t.close();
return false;
}
return true;
}
@Override
public Cursor next() {
return t;
}
@Override
public void remove() {
throw new UnsupportedOperationException("CursorUtils : remove : ");
}
@Override
protected void onCreate() {
t.moveToPosition(-1);
}
};
}
};
}
private static abstract class IteratorWithObject<T> implements Iterator<T> {
protected T t;
public IteratorWithObject(T t) {
this.t = t;
this.onCreate();
}
protected abstract void onCreate();
}
private static abstract class IterableWithObject<T> implements Iterable<T> {
protected T t;
public IterableWithObject(T t) {
this.t = t;
}
}
}