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


Frecuentemente veo código que implica iterar sobre el resultado de una consulta en la 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());
}

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


254
2018-05-23 16:03


origen


Respuestas:


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 esto se mueve al primer resultado si existiera. Si el cursor está vacío o la última fila ya ha sido procesada, entonces el ciclo sale limpiamente.

Por supuesto, no olvide cerrar el cursor una vez que haya terminado con él, preferiblemente en una finally cláusula.

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

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

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

482
2018-05-23 16:03



La mejor forma de mirar 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 luego

EDITAR: La solución dada es excelente si alguna vez necesita repetir 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.


100
2018-05-20 07:00



Me gustaría señalar una tercera alternativa que también funciona si el cursor no está en la posición de inicio:

if (cursor.moveToFirst()) {
    do {
        // do what you need with the cursor here
    } while (cursor.moveToNext());
}

41
2018-03-14 23:08



¿Qué hay de 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;
    }
}
}

9
2017-12-05 03:24



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


6
2018-01-24 11:38



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

5
2018-02-27 13:01



La solución Do / While es más elegante, pero si solo utiliza 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>
    }
}

4
2018-05-12 20:41



if (cursor.getCount() == 0)
  return;

cursor.moveToFirst();

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

cursor.close();

2
2017-07-03 10:19



Inicialmente, el cursor no está en la primera fila mostrar usando moveToNext() puede iterar el cursor cuando la grabación no existe, entonces return falsea menos que return true,

while (cursor.moveToNext()) {
    ...
}

0
2018-06-02 07:36