¿Cuál es la mejor manera de iterar un cursor de Android?

291

Frecuentemente veo código que implica iterar sobre el resultado de una consulta a la base de datos, hacer algo con cada fila y luego pasar a la siguiente fila. 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());
}

Todo esto me parece excesivamente largo, cada uno con múltiples llamadas a Cursormétodos. Seguramente debe haber una manera más ordenada?

Graham Borland
fuente
1
¿Cuál fue el propósito de esto? Lo respondiste tú mismo un minuto después de publicarlo ...
Barak
10
Le respondí al mismo tiempo que lo preguntaba.
Graham Borland
1
Ah, nunca he visto ese enlace antes. Simplemente parecía una tontería hacer una pregunta que aparentemente ya tenía una respuesta.
Barak
55
@Barak: Creo que es genial que haya publicado la publicación, ahora conozco una forma un poco más ordenada de hacer algo, que no hubiera sabido de otra manera.
George
44
Me parece claro que publicaste esto para ayudar a cualquiera que pueda venir a buscar. ¡Gracias por este consejo útil!
muttley91

Respuestas:

515

La forma más simple 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 de manera ordenada.

Por supuesto, no olvide cerrar el cursor una vez que haya terminado, preferiblemente en una finallycláusula.

Cursor cursor = db.rawQuery(...);
try {
    while (cursor.moveToNext()) {
        ...
    }
} finally {
    cursor.close();
}

Si apunta a API 19+, puede usar try-with-resources.

try (Cursor cursor = db.rawQuery(...)) {
    while (cursor.moveToNext()) {
        ...
    }
}
Graham Borland
fuente
19
así que si quisieras hacer esta iteración con un cursor en una posición arbitraria de antemano, ¿usarías cursor.moveToPosition (-1) antes del ciclo while?
Sam
43
¡no olvides cerrarlo!
Simon
13
Una consulta de base de datos SQLite nunca devolverá nulo. Devolverá un cursor vacío si no se encuentran resultados. Sin embargo, las consultas de ContentProvider a veces pueden devolver nulo.
Graham Borland el
8
Solo para agregar algunos centavos ... No compruebe si el cursor tiene datos llamando a moveToFirst () antes de iterar sobre el cursor; perderá la primera entrada
AAverin el
47
Si usa un CursorLoader, asegúrese de llamar cursor.moveToPosition(-1)antes de iterar, porque el cargador reutiliza el cursor cuando cambia la orientación de la pantalla. ¡Pasé una hora rastreando este problema!
Vicky Chijwani
111

La mejor manera 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 dada 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 para un valor dado, sin tener que preocuparse por la posición actual del cursor.

Alex Styl
fuente
8
¿Por qué llamar a tres métodos diferentes, cuando puedes hacerlo con solo uno? ¿Por qué crees que es mejor?
Graham Borland
11
Esta es la forma más segura de hacerlo si está volviendo a cargar un cursor preexistente y desea asegurarse de que su iteración comience desde el principio.
Michael Eilers Smith
99
Estoy de acuerdo en que es más claro que la alternativa más simple. Generalmente prefiero la claridad a la brevedad. Una variación similar con el bucle while - android.codota.com/scenarios/51891850da0a87eb5be3cc22/…
drorw
Después de haber jugado un poco más con los cursores, actualicé mi respuesta. El código dado no es la forma más eficiente de iterar un cursor, pero tiene sus usos. (ver la edición)
Alex Styl
@ AlexStyl ¡Sí, realmente lo hace! Esto salvó mi cordura!
Alessandro
45

Solo 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());
}
Jörg Eisfeld
fuente
1
Hay una verificación redundante. Puede reemplazar if + do-while, con while simple como se indica en la solución aceptada, que también es más simple / más legible.
mtk
66
@mtk no, no es redundante, ese es el punto: si el cursor se está reutilizando, podría estar en una posición, de ahí la necesidad de llamar explícitamente a moveToFirst
Mike Repass
Esto solo es útil si tiene una instrucción else con el registro; de lo contrario, la respuesta de Graham Borland es más concisa.
rds
55
Como señala el comentario de Vicky Chijwani , en el uso en el mundo real, la respuesta de Graham Borland es insegura y requiere moveToPosition(-1). Por lo tanto, ambas respuestas son igualmente consistentes, ya que ambas tienen dos llamadas de cursor. Creo que esta respuesta simplemente supera la respuesta de Borland, ya que no requiere el -1número mágico.
Benjamin
De hecho, creo que es útil en el caso de que desee saber que el cursor no tenía valores, ya que es fácil y tiene sentido agregar un else a la instrucción if aquí.
chacham15 01 de
9

¿Qué tal usar foreach loop:

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;
    }
}
}
aleksander.w1992
fuente
Esta es una solución bastante buena, pero oculta el hecho de que está utilizando la misma Cursorinstancia en cada iteración del bucle.
npace
8

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 una iteración completa y no escaparía a la primera y última iteración.

Pankaj
fuente
6
import java.util.Iterator;
import android.database.Cursor;

public class IterableCursor implements Iterable<Cursor>, Iterator<Cursor> {
    Cursor cursor;
    int toVisit;
    public IterableCursor(Cursor cursor) {
        this.cursor = cursor;
        toVisit = cursor.getCount();
    }
    public Iterator<Cursor> iterator() {
        cursor.moveToPosition(-1);
        return this;
    }
    public boolean hasNext() {
        return toVisit>0;
    }
    public Cursor next() {
    //  if (!hasNext()) {
    //      throw new NoSuchElementException();
    //  }
        cursor.moveToNext();
        toVisit--;
        return cursor;
    }
    public void remove() {
        throw new UnsupportedOperationException();
    }
}

Código de ejemplo:

static void listAllPhones(Context context) {
    Cursor phones = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
    for (Cursor phone : new IterableCursor(phones)) {
        String name = phone.getString(phone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
        String phoneNumber = phone.getString(phone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
        Log.d("name=" + name + " phoneNumber=" + phoneNumber);
    }
    phones.close();
}
18446744073709551615
fuente
+1 para una implementación agradable y compacta. Corrección de errores: iterator()también debería volver a calcular el toVisit = cursor.getCount();uso class IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...que recibe una clase que extends CursorWrapper implements MyInterfaceMyInterface define getters para las propiedades de la base de datos. De esta manera tengo un Iterador basado en cursor <MiInterfaz>
k3b
4

La solución Do / While es más elegante, pero si usa solo la solución While publicada anteriormente, sin moveToPosition (-1), perderá el primer elemento (al menos en la consulta de contacto).

Yo sugiero:

if (cursor.getCount() > 0) {
    cursor.moveToPosition(-1);
    while (cursor.moveToNext()) {
          <do stuff>
    }
}
Lars
fuente
2
if (cursor.getCount() == 0)
  return;

cursor.moveToFirst();

while (!cursor.isAfterLast())
{
  // do something
  cursor.moveToNext();
}

cursor.close();
Changhoon
fuente
2

El cursor es la interfaz que representa una 2-dimensionaltabla de cualquier base de datos.

Cuando intente recuperar algunos datos utilizando una SELECTinstrucción, la base de datos primero creará un objeto CURSOR y le devolverá su referencia.

El puntero de esta referencia devuelta apunta a la ubicación 0 que se llama como antes de la primera ubicación del cursor, por lo que cuando desea recuperar datos del cursor, debe mover primero al primer registro, así que tenemos que usar moveToFirst

Cuando invocas el moveToFirst()método en el cursor, lleva el puntero del cursor a la primera ubicación. Ahora puede acceder a los datos presentes en el primer registro

La mejor forma de mirar:

Cursor del cursor

for (cursor.moveToFirst(); 
     !cursor.isAfterLast();  
     cursor.moveToNext()) {
                  .........
     }
Rajshah
fuente
0

Inicialmente cursor no está en la primera fila Mostrar el uso moveToNext()se puede repetir el cursor al registro no es existir, entonces return false, a menos que return true,

while (cursor.moveToNext()) {
    ...
}
Kundan Kamal
fuente