Travailler avec des listes de données sur Android

Ce document couvre l'utilisation de listes de données dans Firebase. Pour apprendre les bases de la lecture et de l'écriture de données Firebase, consultez Lire et écrire des données sur Android .

Obtenir une référence de base de données

Pour lire et écrire des données à partir de la base de données, vous avez besoin d'une instance de DatabaseReference :

Java

private DatabaseReference mDatabase;
// ...
mDatabase = FirebaseDatabase.getInstance().getReference();

Kotlin+KTX

private lateinit var database: DatabaseReference
// ...
database = Firebase.database.reference

Lire et écrire des listes

Ajouter à une liste de données

Utilisez la méthode push() pour ajouter des données à une liste dans les applications multi-utilisateurs. La méthode push() génère une clé unique chaque fois qu'un nouvel enfant est ajouté à la référence Firebase spécifiée. En utilisant ces clés générées automatiquement pour chaque nouvel élément de la liste, plusieurs clients peuvent ajouter des enfants au même emplacement en même temps sans conflits d'écriture. La clé unique générée par push() est basée sur un horodatage, de sorte que les éléments de la liste sont automatiquement classés par ordre chronologique.

Vous pouvez utiliser la référence aux nouvelles données renvoyées par la méthode push() pour obtenir la valeur de la clé générée automatiquement de l'enfant ou définir des données pour l'enfant. L'appel getKey() sur une référence push() renvoie la valeur de la clé générée automatiquement.

Vous pouvez utiliser ces clés générées automatiquement pour simplifier l'aplatissement de votre structure de données. Pour plus d'informations, consultez l' exemple de répartition des données .

Écoutez les événements pour enfants

Lorsque vous travaillez avec des listes, votre application doit écouter les événements enfants plutôt que les événements de valeur utilisés pour des objets uniques.

Les événements enfants sont déclenchés en réponse à des opérations spécifiques qui se produisent sur les enfants d'un nœud à partir d'une opération telle qu'un nouvel enfant ajouté via la méthode push() ou un enfant mis à jour via la méthode updateChildren() . Chacun de ces ensembles peut être utile pour écouter les modifications apportées à un nœud spécifique dans une base de données.

Pour écouter les événements enfants sur DatabaseReference , attachez un ChildEventListener :

Auditeur Rappel d'événement Utilisation typique
ChildEventListener onChildAdded() Récupérez des listes d'éléments ou écoutez les ajouts à une liste d'éléments. Ce rappel est déclenché une fois pour chaque enfant existant, puis à nouveau chaque fois qu'un nouvel enfant est ajouté au chemin spécifié. Le DataSnapshot transmis à l'écouteur contient les données du nouvel enfant.
onChildChanged() Écoutez les modifications apportées aux éléments d'une liste. Cet événement est déclenché chaque fois qu'un nœud enfant est modifié, y compris toute modification apportée aux descendants du nœud enfant. Le DataSnapshot transmis à l'écouteur d'événement contient les données mises à jour pour l'enfant.
onChildRemoved() Écoutez les éléments supprimés d'une liste. Le DataSnapshot transmis au rappel d'événement contient les données de l'enfant supprimé.
onChildMoved() Écoutez les modifications apportées à l'ordre des éléments dans une liste ordonnée. Cet événement est déclenché chaque fois que le rappel onChildChanged() est déclenché par une mise à jour qui provoque la réorganisation de l'enfant. Il est utilisé avec des données ordonnées avec orderByChild ou orderByValue .

Par exemple, une application de blog social peut utiliser ces méthodes ensemble pour surveiller l'activité dans les commentaires d'un message, comme indiqué ci-dessous :

Java

ChildEventListener childEventListener = new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());

        // A new comment has been added, add it to the displayed list
        Comment comment = dataSnapshot.getValue(Comment.class);

        // ...
    }

    @Override
    public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey());

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so displayed the changed comment.
        Comment newComment = dataSnapshot.getValue(Comment.class);
        String commentKey = dataSnapshot.getKey();

        // ...
    }

    @Override
    public void onChildRemoved(DataSnapshot dataSnapshot) {
        Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey());

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so remove it.
        String commentKey = dataSnapshot.getKey();

        // ...
    }

    @Override
    public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAG, "onChildMoved:" + dataSnapshot.getKey());

        // A comment has changed position, use the key to determine if we are
        // displaying this comment and if so move it.
        Comment movedComment = dataSnapshot.getValue(Comment.class);
        String commentKey = dataSnapshot.getKey();

        // ...
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        Log.w(TAG, "postComments:onCancelled", databaseError.toException());
        Toast.makeText(mContext, "Failed to load comments.",
                Toast.LENGTH_SHORT).show();
    }
};
databaseReference.addChildEventListener(childEventListener);

Kotlin+KTX

val childEventListener = object : ChildEventListener {
    override fun onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?) {
        Log.d(TAG, "onChildAdded:" + dataSnapshot.key!!)

        // A new comment has been added, add it to the displayed list
        val comment = dataSnapshot.getValue<Comment>()

        // ...
    }

    override fun onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?) {
        Log.d(TAG, "onChildChanged: ${dataSnapshot.key}")

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so displayed the changed comment.
        val newComment = dataSnapshot.getValue<Comment>()
        val commentKey = dataSnapshot.key

        // ...
    }

    override fun onChildRemoved(dataSnapshot: DataSnapshot) {
        Log.d(TAG, "onChildRemoved:" + dataSnapshot.key!!)

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so remove it.
        val commentKey = dataSnapshot.key

        // ...
    }

    override fun onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?) {
        Log.d(TAG, "onChildMoved:" + dataSnapshot.key!!)

        // A comment has changed position, use the key to determine if we are
        // displaying this comment and if so move it.
        val movedComment = dataSnapshot.getValue<Comment>()
        val commentKey = dataSnapshot.key

        // ...
    }

    override fun onCancelled(databaseError: DatabaseError) {
        Log.w(TAG, "postComments:onCancelled", databaseError.toException())
        Toast.makeText(context, "Failed to load comments.",
                Toast.LENGTH_SHORT).show()
    }
}
databaseReference.addChildEventListener(childEventListener)

Écoutez les événements de valeur

Bien que l'utilisation d'un ChildEventListener soit la méthode recommandée pour lire des listes de données, il existe des situations où attacher un ValueEventListener à une référence de liste est utile.

Attacher un ValueEventListener à une liste de données renverra la liste complète des données sous la forme d'un seul DataSnapshot , que vous pouvez ensuite boucler pour accéder aux enfants individuels.

Même lorsqu'il n'y a qu'une seule correspondance pour la requête, l'instantané est toujours une liste ; il ne contient qu'un seul élément. Pour accéder à l'élément, vous devez boucler sur le résultat :

Java

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) {
            // TODO: handle the post
        }
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
        // ...
    }
});

Kotlin+KTX

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        for (postSnapshot in dataSnapshot.children) {
            // TODO: handle the post
        }
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
        // ...
    }
})

Ce modèle peut être utile lorsque vous souhaitez récupérer tous les enfants d'une liste en une seule opération, plutôt que d'écouter des événements onChildAdded supplémentaires.

Détacher les auditeurs

Les rappels sont supprimés en appelant la méthode removeEventListener() sur votre référence de base de données Firebase.

Si un écouteur a été ajouté plusieurs fois à un emplacement de données, il est appelé plusieurs fois pour chaque événement et vous devez le détacher le même nombre de fois pour le supprimer complètement.

L'appel removeEventListener() sur un écouteur parent ne supprime pas automatiquement les écouteurs enregistrés sur ses nœuds enfants ; removeEventListener() doit également être appelé sur tous les écouteurs enfants pour supprimer le rappel.

Trier et filtrer les données

Vous pouvez utiliser la classe Realtime Database Query pour récupérer des données triées par clé, par valeur ou par valeur d'un enfant. Vous pouvez également filtrer le résultat trié sur un nombre spécifique de résultats ou sur une plage de clés ou de valeurs.

Trier les données

Pour récupérer des données triées, commencez par spécifier l'une des méthodes de tri pour déterminer comment les résultats sont triés :

Méthode Usage
orderByChild() Triez les résultats en fonction de la valeur d'une clé enfant spécifiée ou d'un chemin enfant imbriqué.
orderByKey() Trier les résultats par clés enfants.
orderByValue() Trier les résultats par valeurs enfants.

Vous ne pouvez utiliser qu'une seule méthode de tri à la fois. L'appel d'une méthode de tri plusieurs fois dans la même requête génère une erreur.

L'exemple suivant montre comment vous pouvez récupérer une liste des meilleurs messages d'un utilisateur triés par nombre d'étoiles :

Java

// My top posts by number of stars
String myUserId = getUid();
Query myTopPostsQuery = databaseReference.child("user-posts").child(myUserId)
        .orderByChild("starCount");
myTopPostsQuery.addChildEventListener(new ChildEventListener() {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
});

Kotlin+KTX

// My top posts by number of stars
val myUserId = uid
val myTopPostsQuery = databaseReference.child("user-posts").child(myUserId)
    .orderByChild("starCount")

myTopPostsQuery.addChildEventListener(object : ChildEventListener {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
})

Cela définit une requête qui, lorsqu'elle est combinée avec un écouteur enfant, synchronise le client avec les messages de l'utilisateur à partir du chemin dans la base de données en fonction de leur ID utilisateur, classés par le nombre d'étoiles que chaque message a reçues. Cette technique d'utilisation des identifiants comme clés d'index est appelée répartition des données, vous pouvez en savoir plus à ce sujet dans Structurez votre base de données .

L'appel à la méthode orderByChild() spécifie la clé enfant par laquelle ordonner les résultats. Dans ce cas, les messages sont triés par la valeur de leur enfant "starCount" respectif. Les requêtes peuvent également être classées par enfants imbriqués, au cas où vous auriez des données qui ressemblent à ceci :

"posts": {
  "ts-functions": {
    "metrics": {
      "views" : 1200000,
      "likes" : 251000,
      "shares": 1200,
    },
    "title" : "Why you should use TypeScript for writing Cloud Functions",
    "author": "Doug",
  },
  "android-arch-3": {
    "metrics": {
      "views" : 900000,
      "likes" : 117000,
      "shares": 144,
    },
    "title" : "Using Android Architecture Components with Firebase Realtime Database (Part 3)",
    "author": "Doug",
  }
},

Dans cet exemple, nous pouvons ordonner nos éléments de liste par valeurs imbriquées sous la clé metrics en spécifiant le chemin relatif vers l'enfant imbriqué dans notre appel orderByChild() .

Java

// Most viewed posts
Query myMostViewedPostsQuery = databaseReference.child("posts")
        .orderByChild("metrics/views");
myMostViewedPostsQuery.addChildEventListener(new ChildEventListener() {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
});

Kotlin+KTX

// Most viewed posts
val myMostViewedPostsQuery = databaseReference.child("posts")
        .orderByChild("metrics/views")
myMostViewedPostsQuery.addChildEventListener(object : ChildEventListener {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
})

Pour plus d'informations sur la façon dont les autres types de données sont triés, voir Comment les données de requête sont triées .

Filtrage des données

Pour filtrer les données, vous pouvez combiner n'importe laquelle des méthodes de limite ou de plage avec une méthode de tri lors de la construction d'une requête.

Méthode Usage
limitToFirst() Définit le nombre maximal d'éléments à renvoyer depuis le début de la liste ordonnée des résultats.
limitToLast() Définit le nombre maximal d'éléments à renvoyer à partir de la fin de la liste ordonnée des résultats.
startAt() Renvoie les éléments supérieurs ou égaux à la clé ou à la valeur spécifiée selon la méthode de tri choisie.
startAfter() Renvoie les éléments supérieurs à la clé ou à la valeur spécifiée en fonction de la méthode de tri choisie.
endAt() Renvoie les éléments inférieurs ou égaux à la clé ou à la valeur spécifiée selon la méthode de tri choisie.
endBefore() Renvoie les éléments inférieurs à la clé ou à la valeur spécifiée en fonction de la méthode de tri choisie.
equalTo() Renvoie les éléments égaux à la clé ou à la valeur spécifiée en fonction de la méthode de tri choisie.

Contrairement aux méthodes de tri, vous pouvez combiner plusieurs fonctions de limite ou de plage. Par exemple, vous pouvez combiner les startAt() et endAt() pour limiter les résultats à une plage de valeurs spécifiée.

Même lorsqu'il n'y a qu'une seule correspondance pour la requête, l'instantané est toujours une liste ; il ne contient qu'un seul élément. Pour accéder à l'élément, vous devez boucler sur le résultat :

Java

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) {
            // TODO: handle the post
        }
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
        // ...
    }
});

Kotlin+KTX

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        for (postSnapshot in dataSnapshot.children) {
            // TODO: handle the post
        }
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
        // ...
    }
})

Limiter le nombre de résultats

Vous pouvez utiliser les limitToFirst() et limitToLast() pour définir un nombre maximum d'enfants à synchroniser pour un rappel donné. Par exemple, si vous utilisez limitToFirst() pour définir une limite de 100, vous ne recevez initialement que 100 onChildAdded() . Si vous avez moins de 100 éléments stockés dans votre base de données Firebase, un rappel onChildAdded() se déclenche pour chaque élément.

Au fur et à mesure que les éléments changent, vous recevez onChildAdded() pour les éléments qui entrent dans la requête et onChildRemoved() pour les éléments qui en sortent afin que le nombre total reste à 100.

L'exemple suivant montre comment l'exemple d'application de blog définit une requête pour récupérer une liste des 100 messages les plus récents de tous les utilisateurs :

Java

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys
Query recentPostsQuery = databaseReference.child("posts")
        .limitToFirst(100);

Kotlin+KTX

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys.
databaseReference.child("posts").limitToFirst(100)

Cet exemple définit uniquement une requête, pour synchroniser réellement les données dont elle a besoin pour avoir un écouteur attaché.

Filtrer par clé ou valeur

Vous pouvez utiliser startAt() , startAfter() , endAt() , endBefore() et equalTo() pour choisir des points de départ, de fin et d'équivalence arbitraires pour les requêtes. Cela peut être utile pour paginer des données ou rechercher des éléments avec des enfants qui ont une valeur spécifique.

Comment les données de la requête sont triées

Cette section explique comment les données sont triées par chacune des méthodes de tri dans la classe Query .

orderByChild

Lors de l'utilisation orderByChild() , les données contenant la clé enfant spécifiée sont triées comme suit :

  1. Les enfants avec une valeur null pour la clé enfant spécifiée viennent en premier.
  2. Les enfants avec une valeur false pour la clé enfant spécifiée viennent ensuite. Si plusieurs enfants ont la valeur false , ils sont triés lexicographiquement par clé.
  3. Les enfants avec une valeur true pour la clé enfant spécifiée viennent ensuite. Si plusieurs enfants ont la valeur true , ils sont triés lexicographiquement par clé.
  4. Les enfants avec une valeur numérique viennent ensuite, triés par ordre croissant. Si plusieurs enfants ont la même valeur numérique pour le nœud enfant spécifié, ils sont triés par clé.
  5. Les chaînes viennent après les nombres et sont triées lexicographiquement par ordre croissant. Si plusieurs enfants ont la même valeur pour le nœud enfant spécifié, ils sont triés lexicographiquement par clé.
  6. Les objets viennent en dernier et sont triés lexicographiquement par clé dans l'ordre croissant.

orderByKey

Lorsque vous utilisez orderByKey() pour trier vos données, les données sont renvoyées dans l'ordre croissant par clé.

  1. Les enfants avec une clé qui peut être analysée comme un entier 32 bits viennent en premier, triés par ordre croissant.
  2. Les enfants avec une valeur de chaîne comme clé viennent ensuite, triés lexicographiquement par ordre croissant.

orderByValue

Lors de l'utilisation orderByValue() , les enfants sont triés par leur valeur. Les critères de classement sont les mêmes que dans orderByChild() , sauf que la valeur du nœud est utilisée à la place de la valeur d'une clé enfant spécifiée.

Prochaines étapes