Qual é a melhor maneira de iterar um Cursor do Android?

291

Frequentemente, vejo o código que envolve a iteração sobre o resultado de uma consulta ao banco de dados, fazendo algo com cada linha e passando para a próxima linha. Exemplos típicos são os seguintes.

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

Tudo isso me parece excessivamente longo, cada um com várias chamadas para Cursormétodos. Certamente deve haver uma maneira mais limpa?

Graham Borland
fonte
1
Qual foi o propósito disso? Você respondeu-lo sozinho dentro de um minuto de publicá-la ...
Barak
10
Eu atendi ao mesmo tempo em que pedi.
Graham Borland
1
Ah, nunca vi esse link antes. Pareceu uma tolice fazer uma pergunta para a qual você aparentemente já tinha uma resposta.
Barak
5
@Barak: Eu acho ótimo que ele tenha postado o post - agora eu sei uma maneira um pouco mais clara de fazer algo, que eu não saberia de outra maneira.
George
4
Parece-me claro que você postou isso para ajudar alguém que possa vir procurar. Props para você por isso, e obrigado pela dica útil!
precisa saber é o seguinte

Respostas:

515

A maneira mais simples é esta:

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

O cursor inicia antes da primeira linha do resultado, portanto, na primeira iteração, ele se move para o primeiro resultado, se existir . Se o cursor estiver vazio ou a última linha já tiver sido processada, o loop será encerrado ordenadamente.

Obviamente, não se esqueça de fechar o cursor quando terminar, de preferência em uma finallycláusula.

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

Se você segmentar a API 19+, poderá usar a tentativa com recursos.

try (Cursor cursor = db.rawQuery(...)) {
    while (cursor.moveToNext()) {
        ...
    }
}
Graham Borland
fonte
19
portanto, se você quisesse fazer essa iteração com um cursor em uma posição abreviada anteriormente, usaria cursor.moveToPosition (-1) antes do loop while?
16333 Sam
43
não esqueça de fechá-lo!
amigos estão
13
Uma consulta ao banco de dados SQLite nunca retornará nulo. Ele retornará um Cursor vazio se nenhum resultado for encontrado. Às vezes, as consultas ContentProvider podem retornar nulas.
Graham Borland
8
Só para acrescentar alguns centavos ... não verificar se cursor tem dados chamando moveToFirst () antes de você está indo para iterar cursor - você vai perder a primeira entrada
AAverin
47
Se estiver usando a CursorLoader, certifique-se de ligar cursor.moveToPosition(-1)antes da iteração, porque o carregador reutiliza o cursor quando a orientação da tela muda. Passou uma hora rastreando esse problema!
Vicky Chijwani
111

A maneira mais bonita que encontrei para percorrer um cursor é a seguinte:

Cursor cursor;
... //fill the cursor here

for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
    // do what you need with the cursor here
}

Não esqueça de fechar o cursor depois

EDIT: A solução fornecida é ótima se você precisar iterar um cursor do qual você não é responsável. Um bom exemplo seria se você estiver usando um cursor como argumento em um método e precisar varrer o cursor em busca de um determinado valor, sem precisar se preocupar com a posição atual do cursor.

Alex Styl
fonte
8
Por que chamar três métodos diferentes, quando você pode fazer isso com apenas um? Por que você acha que isso é melhor?
Graham Borland
11
Esse é o caminho mais seguro a seguir, se você estiver recarregando um cursor preexistente e quiser ter certeza de que sua iteração começa desde o início.
Michael Eilers Smith
9
Concordo que é mais claro que a alternativa mais simples. Geralmente sou a favor da clareza à concisão. Uma variação semelhante com o loop while - android.codota.com/scenarios/51891850da0a87eb5be3cc22/…
drorw:
Tendo jogado um pouco mais com os cursores, atualizei minha resposta. O código fornecido com certeza não é a maneira mais eficiente de iterar um cursor, mas tem seus usos. (veja a edição) #
7894 Alex Styl
@AlexStyl Sim, é verdade! Isso salvou minha sanidade!
Alessandro
45

Gostaria apenas de apontar uma terceira alternativa que também funciona se o cursor não estiver na posição inicial:

if (cursor.moveToFirst()) {
    do {
        // do what you need with the cursor here
    } while (cursor.moveToNext());
}
Jörg Eisfeld
fonte
1
Há uma verificação redundante. Você pode substituir o if + do-while, por simple enquanto indicado na solução aceita, que também é mais simples / mais legível.
mtk
6
@mtk não, não é redundante, esse é o ponto - se o cursor está sendo reutilizado, ele pode estar em uma posição, daí a necessidade de explicitamente chamar moveToFirst
Mike Repasse
Isso só é útil se você tiver uma instrução else com o log; caso contrário, a resposta de Graham Borland é mais concisa.
rds 22/07
5
Como aponta o comentário de Vicky Chijwani , no uso no mundo real, a resposta de Graham Borland é insegura e requer moveToPosition(-1). Portanto, ambas as respostas são igualmente consistentes, pois ambas têm duas chamadas de cursor. Eu acho que essa resposta apenas delineia a resposta de Borland, pois não requer o -1número mágico.
Benjamin Benjamin
Na verdade, acho que é útil no caso de você querer saber que o cursor não tinha valores, pois é fácil e faz sentido adicionar um outro à declaração if aqui.
precisa saber é o seguinte
9

Que tal usar o loop foreach:

Cursor cursor;
for (Cursor c : CursorUtils.iterate(cursor)) {
    //c.doSth()
}

No entanto, minha versão do CursorUtils deve ser menos feia, mas fecha automaticamente o 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
fonte
Essa é uma solução bastante interessante, mas oculta o fato de você estar usando a mesma Cursorinstância em cada iteração do loop.
N /
8

Abaixo pode ser a melhor maneira:

if (cursor.moveToFirst()) {
   while (!cursor.isAfterLast()) {
         //your code to implement
         cursor.moveToNext();
    }
}
cursor.close();

O código acima garantiria que passaria por toda a iteração e não escaparia da primeira e da última iteração.

Pankaj
fonte
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 exemplo:

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
fonte
+1 para uma implementação agradável e compacta. Correção: iterator()também deve recalcular o toVisit = cursor.getCount();uso class IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...que recebe uma classe em que extends CursorWrapper implements MyInterfaceMyInterface define getters para propriedades do banco de dados. Desta forma, eu tenho um cursor com base Iterator <MyInterface>
k3b
4

A solução Do / While é mais elegante, mas se você usar apenas a solução While postada acima, sem a moveToPosition (-1), você perderá o primeiro elemento (pelo menos na consulta de contato).

Eu sugiro:

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

cursor.moveToFirst();

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

cursor.close();
Changhoon
fonte
2

O cursor é a interface que representa um2-dimensional tabela de qualquer banco de dados.

Quando você tenta recuperar alguns dados usando a SELECTinstrução, o banco de dados cria primeiro um objeto CURSOR e retorna sua referência a você.

O ponteiro desta referência retornada está apontando para o 0º local que, de outra forma, é chamado como antes do primeiro local do Cursor. Portanto, quando você deseja recuperar dados do cursor, deve primeiro mover-se para o 1º registro, para que possamos usar moveToFirst

Quando você invoca o moveToFirst()método no Cursor, ele leva o ponteiro do cursor para o 1º local. Agora você pode acessar os dados presentes no 1º registro

A melhor maneira de olhar:

Cursor do cursor

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

Inicialmente, o cursor não está no primeiro show de linha usando. moveToNext()Você pode iterar o cursor quando o registro não existir return false, a menos que return true,

while (cursor.moveToNext()) {
    ...
}
kundan kamal
fonte