Quelle est la meilleure façon d'itérer un curseur Android?

291

Je vois souvent du code qui implique d'itérer sur le résultat d'une requête de base de données, de faire quelque chose avec chaque ligne, puis de passer à la ligne suivante. Les exemples typiques sont les suivants.

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

Tout cela me semble excessivement long, chacun avec de multiples appels aux Cursorméthodes. Il doit sûrement y avoir un moyen plus soigné?

Graham Borland
la source
1
Quel en était le but? Vous y avez répondu vous-même une minute après l'avoir posté ...
Barak
10
Je lui ai répondu en même temps que je le demandais.
Graham Borland
1
Ah, jamais vu ce lien auparavant. Il semblait juste stupide de poser une question à laquelle vous aviez apparemment déjà eu une réponse.
Barak
5
@Barak: Je pense que c'est génial qu'il ait publié le post - maintenant je connais une façon un peu plus soignée de faire quelque chose, que je n'aurais pas su autrement.
George
4
Il me semble clair que vous avez posté ceci pour aider tous ceux qui pourraient venir à la recherche. Je vous remercie pour cela et merci pour le conseil utile!
muttley91

Réponses:

515

La façon la plus simple est la suivante:

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

Le curseur commence avant la première ligne de résultat, donc à la première itération, il passe au premier résultat s'il existe . Si le curseur est vide ou si la dernière ligne a déjà été traitée, la boucle se termine proprement.

Bien sûr, n'oubliez pas de fermer le curseur une fois que vous en avez terminé, de préférence dans une finallyclause.

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

Si vous ciblez l'API 19+, vous pouvez utiliser try-with-resources.

try (Cursor cursor = db.rawQuery(...)) {
    while (cursor.moveToNext()) {
        ...
    }
}
Graham Borland
la source
19
donc si vous vouliez faire cette itération avec un curseur dans une position abritaire au préalable, vous utiliseriez cursor.moveToPosition (-1) avant la boucle while?
Sam
43
n'oubliez pas de le fermer!
simon
13
Une requête de base de données SQLite ne renverra jamais null. Il renverra un curseur vide si aucun résultat n'est trouvé. Les requêtes ContentProvider peuvent parfois retourner null, cependant.
Graham Borland
8
Juste pour ajouter quelques centimes ... Ne vérifiez pas si le curseur contient des données en appelant moveToFirst () avant de parcourir le curseur - vous perdrez la première entrée
AAverin
47
Si vous utilisez un CursorLoader, assurez-vous d'appeler cursor.moveToPosition(-1)avant l'itération, car le chargeur réutilise le curseur lorsque l'orientation de l'écran change. J'ai passé une heure à rechercher ce problème!
Vicky Chijwani
111

La meilleure façon que j'ai trouvée de passer par un curseur est la suivante:

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

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

N'oubliez pas de fermer le curseur par la suite

EDIT: La solution donnée est idéale si vous avez besoin d'itérer un curseur dont vous n'êtes pas responsable. Un bon exemple serait, si vous prenez un curseur comme argument dans une méthode, et que vous devez scanner le curseur pour une valeur donnée, sans avoir à vous soucier de la position actuelle du curseur.

Alex Styl
la source
8
Pourquoi appeler trois méthodes différentes, alors que vous pouvez le faire avec une seule? Pourquoi pensez-vous que c'est mieux?
Graham Borland
11
C'est le moyen le plus sûr si vous rechargez un curseur préexistant et que vous voulez être sûr que votre itération commence depuis le début.
Michael Eilers Smith
9
Je suis d'accord que c'est plus clair que l'alternative plus simple. Je préfère généralement la clarté à la brièveté. Une variation similaire avec la boucle while - android.codota.com/scenarios/51891850da0a87eb5be3cc22/…
drorw
Ayant joué un peu plus avec les curseurs, j'ai mis à jour ma réponse. Le code donné n'est certainement pas le moyen le plus efficace d'itérer un curseur, mais il a ses usages. (voir l'édition)
Alex Styl
@AlexStyl Oui, c'est vraiment le cas! Cela a sauvé ma raison!
Alessandro
45

Je voudrais juste souligner une troisième alternative qui fonctionne également si le curseur n'est pas à la position de départ:

if (cursor.moveToFirst()) {
    do {
        // do what you need with the cursor here
    } while (cursor.moveToNext());
}
Jörg Eisfeld
la source
1
Il y a un chèque redondant. Vous pouvez remplacer if + do-while, par simple while comme indiqué dans la solution acceptée, qui est également plus simple / plus lisible.
mtk
6
@mtk non, ce n'est pas redondant, c'est le point - si le curseur est réutilisé, il pourrait être à une position, d'où la nécessité d'appeler explicitement moveToFirst
Mike Repass
Ceci n'est utile que si vous avez une instruction else avec journalisation; sinon, la réponse de Graham Borland est plus concise.
rds
5
Comme le souligne le commentaire de Vicky Chijwani , dans la réalité, la réponse de Graham Borland n'est pas sûre et nécessite moveToPosition(-1). Ainsi, les deux réponses sont également concises car elles ont toutes deux deux appels de curseur. Je pense que cette réponse tranche juste la réponse de Borland car elle ne nécessite pas le -1nombre magique.
Benjamin
En fait, je pense que c'est utile dans le cas où vous voulez savoir que le curseur n'a pas de valeurs car c'est facile et il est logique d'ajouter un autre à l'instruction if ici.
chacham15
9

Que diriez-vous d'utiliser la boucle foreach:

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

Cependant ma version de CursorUtils devrait être moins laide, mais elle ferme automatiquement le curseur:

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
la source
C'est une solution plutôt cool, mais elle cache le fait que vous utilisez la même Cursorinstance à chaque itération de la boucle.
npace
8

Ci-dessous pourrait être le meilleur moyen:

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

Le code ci-dessus garantirait qu'il passerait par une itération entière et n'échapperait pas à la première et à la dernière itération.

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

Exemple de code:

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
la source
+1 pour une mise en œuvre agréable et compacte. Bugfix: iterator()devrait également recalculer toVisit = cursor.getCount();j'utilise class IterableCursor<T extends Cursor> implements Iterable<T>, Iterator<T> {...qui reçoit une classe extends CursorWrapper implements MyInterfaceoù MyInterface définit des getters pour les propriétés de la base de données. De cette façon, j'ai un itérateur basé sur le curseur <MyInterface>
k3b
4

La solution Do / While est plus élégante, mais si vous utilisez uniquement la solution While publiée ci-dessus, sans le moveToPosition (-1), vous manquerez le premier élément (au moins sur la requête Contact).

Je suggère:

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

cursor.moveToFirst();

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

cursor.close();
Changhoon
la source
2

Le curseur est l'interface qui représente une 2-dimensionaltable de n'importe quelle base de données.

Lorsque vous essayez de récupérer des données à l'aide d'une SELECTinstruction, la base de données crée d'abord un objet CURSOR et vous renvoie sa référence.

Le pointeur de cette référence renvoyée pointe vers le 0ème emplacement qui est autrement appelé comme avant le premier emplacement du curseur. moveToFirst

Lorsque vous appelez la moveToFirst()méthode sur le curseur, il place le pointeur du curseur au premier emplacement. Vous pouvez maintenant accéder aux données présentes dans le 1er enregistrement

La meilleure façon de regarder:

Curseur du curseur

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

Initialement, le curseur n'est pas sur la première ligne à l'aide de moveToNext()vous pouvez itérer le curseur lorsque l'enregistrement n'existe pas, alors return false, à moins que cela return true,

while (cursor.moveToNext()) {
    ...
}
kundan kamal
la source