Lire et écrire des données

(Facultatif) Prototyper et tester avec la suite d'émulateurs Firebase

Avant de parler de la façon dont votre application lit et écrit dans Realtime Database, présentons un ensemble d'outils que vous pouvez utiliser pour créer des prototypes et tester les fonctionnalités de Realtime Database : la suite d'émulateurs Firebase. Si vous essayez différents modèles de données, optimisez vos règles de sécurité ou cherchez le moyen le plus rentable d'interagir avec le backend, il peut être judicieux de pouvoir travailler en local sans déployer de services en direct.

Un émulateur Realtime Database fait partie de la suite d'émulateurs, permet à votre application d'interagir avec le contenu et la configuration de votre base de données émulée, et éventuellement les ressources de projet émulées (fonctions, autres bases de données, et règles de sécurité).emulator_suite_short

L'utilisation de l'émulateur Realtime Database ne nécessite que quelques étapes :

  1. Ajoutez une ligne de code à la configuration de test de votre application pour vous connecter à l'émulateur.
  2. À partir de la racine du répertoire de votre projet local, exécutez firebase emulators:start.
  3. Effectuer des appels depuis le code du prototype de votre application à l'aide d'une plate-forme Realtime Database SDK comme d'habitude, ou à l'aide de l'API REST Realtime Database.

Un tutoriel détaillé impliquant Realtime Database et Cloud Functions est disponible. Vous pouvez également consulter l'introduction à la suite d'émulateurs.

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

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

DatabaseReference ref = FirebaseDatabase.instance.ref();

Écrire des données

Ce document présente les principes de base de la lecture et de l'écriture de données Firebase.

Les données Firebase sont écrites dans un DatabaseReference et récupérées par en attente ou à l'écoute des événements émis par la référence. Les événements sont émis une fois pour l'état initial des données, puis chaque fois que les données changent.

Opérations d'écriture de base

Pour les opérations d'écriture de base, vous pouvez utiliser set() pour enregistrer des données dans une référence spécifiée, en remplaçant toutes les données existantes à ce chemin d'accès. Vous pouvez définir une référence aux types suivants: String, boolean, int, double, Map, List.

Par exemple, vous pouvez ajouter un utilisateur avec set() comme suit :

DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");

await ref.set({
  "name": "John",
  "age": 18,
  "address": {
    "line1": "100 Mountain View"
  }
});

L'utilisation de set() de cette manière écrase les données à l'emplacement spécifié, y compris les nœuds enfants. Toutefois, vous pouvez toujours mettre à jour un élément enfant à réécrire l'objet entier. Si vous souhaitez autoriser les utilisateurs à mettre à jour leur profil, vous pouvez modifier le nom d'utilisateur comme suit :

DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");

// Only update the age, leave the name and address!
await ref.update({
  "age": 19,
});

La méthode update() accepte un sous-chemin vers les nœuds, ce qui vous permet de mettre à jour plusieurs les nœuds de la base de données en même temps:

DatabaseReference ref = FirebaseDatabase.instance.ref("users");

await ref.update({
  "123/age": 19,
  "123/address/line1": "1 Mountain View",
});

Lire des données

Lire les données en écoutant les événements de valeur

Pour lire des données à un chemin d'accès et écouter les modifications, utilisez la propriété onValue de DatabaseReference pour écouter les DatabaseEvent.

Vous pouvez utiliser DatabaseEvent pour lire les données à un chemin d'accès donné, tel qu'il existe au moment de l'événement. Cet événement est déclenché une fois lorsque l'écouteur est associé, puis chaque fois que les données, y compris les enfants, changent. L'événement comporte une propriété snapshot contenant toutes les données à cet emplacement, y compris les données enfants. S'il n'y a pas de données, La propriété exists sera false, et sa propriété value sera nulle.

L'exemple suivant montre une application de blog social qui récupère les détails d'un post à partir de la base de données :

DatabaseReference starCountRef =
        FirebaseDatabase.instance.ref('posts/$postId/starCount');
starCountRef.onValue.listen((DatabaseEvent event) {
    final data = event.snapshot.value;
    updateStarCount(data);
});

L'écouteur reçoit un DataSnapshot qui contient les données à l'emplacement spécifié dans la base de données au moment de l'événement dans sa propriété value.

Lire les données une fois

Lire une fois à l'aide de get()

Le SDK est conçu pour gérer les interactions avec les serveurs de base de données, que votre l'application est en ligne ou hors connexion.

En règle générale, vous devez utiliser les techniques d'événements de valeur décrites ci-dessus pour lire pour être averti des mises à jour des données du backend. Ces techniques de réduire votre utilisation et votre facturation, et sont optimisés pour offrir à vos utilisateurs tant en ligne que hors connexion.

Si vous n'avez besoin des données qu'une seule fois, vous pouvez utiliser get() pour obtenir un instantané des données de la base de données. Si, pour une raison quelconque, get() ne parvient pas à renvoyer le "server", le client va vérifier le cache du stockage local et renvoyer une erreur si la valeur est toujours introuvable.

L'exemple suivant montre comment récupérer le nom d'utilisateur public d'un utilisateur une seule fois à partir de la base de données :

final ref = FirebaseDatabase.instance.ref();
final snapshot = await ref.child('users/$userId').get();
if (snapshot.exists) {
    print(snapshot.value);
} else {
    print('No data available.');
}

Une utilisation inutile de get() peut augmenter l'utilisation de la bande passante et entraîner une perte de performances, ce qui peut être évité en utilisant un écouteur en temps réel, comme indiqué ci-dessus.

Lire les données une fois avec once()

Dans certains cas, vous pouvez souhaiter que la valeur du cache local soit renvoyée immédiatement, au lieu de rechercher une valeur mise à jour sur le serveur. Dans ces cas, vous pouvez utiliser once() pour obtenir les données du cache du disque local immédiatement.

Cela est utile pour les données qui n'ont besoin d'être chargées qu'une seule fois et qui ne devraient pas changent fréquemment ou nécessitent une écoute active. Par exemple, l'application de blog utilise cette méthode pour charger le profil d'un utilisateur commencez à rédiger un nouveau message:

final event = await ref.once(DatabaseEventType.value);
final username = event.snapshot.value?.username ?? 'Anonymous';

Mettre à jour ou supprimer des données

Modifier des champs spécifiques

Pour écrire simultanément sur des enfants spécifiques d'un nœud sans écraser d'autres les nœuds enfants, utilisez la méthode update().

Lorsque vous appelez update(), vous pouvez mettre à jour les valeurs enfants de niveau inférieur en spécifiant un chemin d'accès pour la clé. Si les données sont stockées dans plusieurs emplacements pour une meilleure évolutivité, vous pouvez mettre à jour toutes les instances de ces données à l'aide de la diffusion des données. Par exemple, un application de blog social peut vouloir créer un article et le mettre à jour simultanément pour le flux d'activité récente et le flux d'activité de l'utilisateur ayant publié la publication. Pour ce faire, de blog utilise un code de ce type:

void writeNewPost(String uid, String username, String picture, String title,
        String body) async {
    // A post entry.
    final postData = {
        'author': username,
        'uid': uid,
        'body': body,
        'title': title,
        'starCount': 0,
        'authorPic': picture,
    };

    // Get a key for a new Post.
    final newPostKey =
        FirebaseDatabase.instance.ref().child('posts').push().key;

    // Write the new post's data simultaneously in the posts list and the
    // user's post list.
    final Map<String, Map> updates = {};
    updates['/posts/$newPostKey'] = postData;
    updates['/user-posts/$uid/$newPostKey'] = postData;

    return FirebaseDatabase.instance.ref().update(updates);
}

Cet exemple utilise push() pour créer un post dans le nœud contenant des posts pour tous les utilisateurs à /posts/$postid et récupérer simultanément la clé avec key. La clé peut ensuite être utilisée pour créer une deuxième entrée dans le posts à /user-posts/$userid/$postid.

Ces chemins d'accès vous permettent d'effectuer des mises à jour simultanées de plusieurs emplacements dans l'arborescence JSON avec un seul appel à update(), comme dans cet exemple crée le post aux deux emplacements. Mises à jour simultanées effectuées de cette manière sont atomiques: toutes les mises à jour réussissent ou échouent.

Ajouter un rappel de fin

Si vous souhaitez savoir quand vos données ont été validées, vous pouvez enregistrer des rappels de fin. set() et update() renvoient des Future, auxquels vous pouvez associer des rappels de réussite et d'erreur qui sont appelés lorsque l'écriture a été validée dans la base de données et lorsque l'appel a échoué.

FirebaseDatabase.instance
    .ref('users/$userId/email')
    .set(emailAddress)
    .then((_) {
        // Data saved successfully!
    })
    .catchError((error) {
        // The write failed...
    });

Supprimer des données

Le moyen le plus simple de supprimer des données consiste à appeler remove() au niveau d'une référence à l'emplacement de ces données.

Vous pouvez également supprimer en spécifiant "null" comme valeur pour une autre opération d'écriture, telle que set() ou update(). Vous pouvez utiliser cette technique avec update() pour supprimer plusieurs enfants dans un seul appel d'API.

Enregistrer les données en tant que transactions

Lorsque vous travaillez avec des données qui pourraient être corrompues par des modifications simultanées, comme des compteurs incrémentiels, vous pouvez utiliser une transaction en transmettant gestionnaire de transactions vers runTransaction(). Un gestionnaire de transactions utilise l'état actuel des données en tant qu'argument et renvoie le nouvel état souhaité que vous souhaitez écrire. Si un autre client écrit sur l'emplacement avant que la nouvelle valeur ne soit écrite, votre "update" est de nouveau appelée avec la nouvelle valeur actuelle, et l'écriture est nouvelle tentative.

Dans notre exemple d'application de blog sur les réseaux sociaux, vous pourriez autoriser les utilisateurs à activer le suivi et supprimer le suivi des posts et suivre leur nombre d'étoiles, comme suit:

void toggleStar(String uid) async {
  DatabaseReference postRef =
      FirebaseDatabase.instance.ref("posts/foo-bar-123");

  TransactionResult result = await postRef.runTransaction((Object? post) {
    // Ensure a post at the ref exists.
    if (post == null) {
      return Transaction.abort();
    }

    Map<String, dynamic> _post = Map<String, dynamic>.from(post as Map);
    if (_post["stars"] is Map && _post["stars"][uid] != null) {
      _post["starCount"] = (_post["starCount"] ?? 1) - 1;
      _post["stars"][uid] = null;
    } else {
      _post["starCount"] = (_post["starCount"] ?? 0) + 1;
      if (!_post.containsKey("stars")) {
        _post["stars"] = {};
      }
      _post["stars"][uid] = true;
    }

    // Return the new data.
    return Transaction.success(_post);
  });
}

Par défaut, des événements sont déclenchés chaque fois que la fonction de mise à jour des transactions s'exécute, Si vous exécutez la fonction plusieurs fois, vous pouvez voir des états intermédiaires. Vous pouvez définir applyLocally sur false pour supprimer ces états intermédiaires et À la place, attendez la fin de la transaction avant que des événements ne soient déclenchés:

await ref.runTransaction((Object? post) {
  // ...
}, applyLocally: false);

Le résultat d'une transaction est un TransactionResult, qui contient des informations par exemple si la transaction a été validée ou si le nouvel instantané a été créé:

DatabaseReference ref = FirebaseDatabase.instance.ref("posts/123");

TransactionResult result = await ref.runTransaction((Object? post) {
  // ...
});

print('Committed? ${result.committed}'); // true / false
print('Snapshot? ${result.snapshot}'); // DataSnapshot

Annuler une transaction

Si vous souhaitez annuler une transaction de manière sécurisée, appelez Transaction.abort() pour générer une exception AbortTransactionException :

TransactionResult result = await ref.runTransaction((Object? user) {
  if (user !== null) {
    return Transaction.abort();
  }

  // ...
});

print(result.committed); // false

Incréments atomiques côté serveur

Dans le cas d'utilisation ci-dessus, nous écrivons deux valeurs dans la base de données: l'identifiant l'utilisateur qui ajoute ou désactive le suivi, ainsi que le nombre d'étoiles incrémenté. Si nous savez déjà que l'utilisateur ajoute le post aux favoris, nous pouvons utiliser un incrément atomique au lieu d'une transaction.

void addStar(uid, key) async {
  Map<String, Object?> updates = {};
  updates["posts/$key/stars/$uid"] = true;
  updates["posts/$key/starCount"] = ServerValue.increment(1);
  updates["user-posts/$key/stars/$uid"] = true;
  updates["user-posts/$key/starCount"] = ServerValue.increment(1);
  return FirebaseDatabase.instance.ref().update(updates);
}

Ce code n'utilise pas d'opération de transaction. Il n'est donc pas automatiquement réexécuté en cas de mise à jour en conflit. Toutefois, comme l'opération d'incrémentation se produit directement sur le serveur de base de données, il n'y a aucun risque de conflit.

Si vous souhaitez détecter et rejeter les conflits spécifiques à l'application, par exemple lorsqu'un utilisateur ajoute une étoile à un post auquel il a déjà ajouté une étoile, vous devez écrire des règles de sécurité personnalisées pour ce cas d'utilisation.

Utiliser des données hors connexion

Si un client perd sa connexion réseau, votre application continue de fonctionner correctement.

Chaque client connecté à une base de données Firebase gère sa propre version interne de toutes les données actives. Lorsque des données sont écrites, elles le sont d'abord dans cette version locale. Le client Firebase synchronise ensuite ces données avec les serveurs de base de données distants et avec d'autres clients dans la mesure du possible.

Par conséquent, toutes les écritures dans la base de données déclenchent immédiatement des événements locaux, avant que des données ne soient écrites sur le serveur. Cela signifie que votre application quelle que soit la latence ou la connectivité du réseau.

Une fois la connectivité rétablie, votre application reçoit l'ensemble approprié de afin que le client se synchronise avec l'état actuel du serveur, sans avoir à et écrire n'importe quel code personnalisé.

Nous parlerons plus en détail du comportement hors connexion dans En savoir plus sur les fonctionnalités en ligne et hors connexion

Étapes suivantes