Lire et écrire des données sur le Web

(Facultatif) Prototypez et testez avec Firebase Local Emulator Suite

Avant de parler de la façon dont votre application lit et écrit dans la base de données en temps réel, présentons un ensemble d'outils que vous pouvez utiliser pour prototyper et tester les fonctionnalités de la base de données en temps réel : Firebase Local Emulator Suite. Si vous essayez différents modèles de données, optimisez vos règles de sécurité ou travaillez pour trouver le moyen le plus rentable d'interagir avec le back-end, pouvoir travailler localement sans déployer de services en direct peut être une excellente idée.

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

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

  1. Ajout d'une ligne de code à la configuration de test de votre application pour se connecter à l'émulateur.
  2. À partir de la racine de votre répertoire de projet local, exécutez firebase emulators:start .
  3. Effectuer des appels à partir du code prototype de votre application à l'aide d'un SDK de plate-forme de base de données en temps réel, comme d'habitude, ou à l'aide de l'API REST de base de données en temps réel.

Une procédure pas à pas détaillée impliquant la base de données en temps réel et les fonctions cloud est disponible. Vous devriez également jeter un œil à l'introduction de Local Emulator Suite .

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 firebase.database.Reference :

Web version 9

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web version 8

var database = firebase.database();

Écrire des données

Ce document couvre les bases de la récupération de données et comment trier et filtrer les données Firebase.

Les données Firebase sont récupérées en attachant un écouteur asynchrone à un firebase.database.Reference . L'écouteur est déclenché une fois pour l'état initial des données et à nouveau 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 sur ce chemin. Par exemple, une application de blog social peut ajouter un utilisateur avec set() comme suit :

Web version 9

import { getDatabase, ref, set } from "firebase/database";

function writeUserData(userId, name, email, imageUrl) {
  const db = getDatabase();
  set(ref(db, 'users/' + userId), {
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

Web version 8

function writeUserData(userId, name, email, imageUrl) {
  firebase.database().ref('users/' + userId).set({
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

L'utilisation set() écrase les données à l'emplacement spécifié, y compris les nœuds enfants.

Lire les données

Écoutez les événements de valeur

Pour lire les données d'un chemin et écouter les modifications, utilisez onValue() pour observer les événements. Vous pouvez utiliser cet événement pour lire des instantanés statiques du contenu d'un chemin donné, tels qu'ils existaient au moment de l'événement. Cette méthode est déclenchée une fois lorsque l'écouteur est attaché et à chaque fois que les données, y compris les enfants, changent. Le rappel d'événement reçoit un instantané contenant toutes les données à cet emplacement, y compris les données enfants. S'il n'y a pas de données, l'instantané renverra false lorsque vous appelez exists() et null lorsque vous appelez val() dessus.

L'exemple suivant illustre une application de blog social récupérant le nombre d'étoiles d'un article dans la base de données :

Web version 9

import { getDatabase, ref, onValue} from "firebase/database";

const db = getDatabase();
const starCountRef = ref(db, 'posts/' + postId + '/starCount');
onValue(starCountRef, (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

Web version 8

var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

L'écouteur reçoit un snapshot qui contient les données à l'emplacement spécifié dans la base de données au moment de l'événement. Vous pouvez récupérer les données dans l' snapshot avec la méthode val() .

Lire les données une fois

Lire les données une fois avec get()

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

En règle générale, vous devez utiliser les techniques d'événement de valeur décrites ci-dessus pour lire les données afin d'être informé des mises à jour des données à partir du backend. Les techniques d'écoute réduisent votre utilisation et votre facturation, et sont optimisées pour offrir à vos utilisateurs la meilleure expérience lorsqu'ils sont en ligne et hors ligne.

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() est incapable de renvoyer la valeur du serveur, le client sondera le cache de stockage local et renverra une erreur si la valeur n'est toujours pas trouvée.

L'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.

Web version 9

import { getDatabase, ref, child, get } from "firebase/database";

const dbRef = ref(getDatabase());
get(child(dbRef, `users/${userId}`)).then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

Web version 8

const dbRef = firebase.database().ref();
dbRef.child("users").child(userId).get().then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

Lire les données une fois avec un observateur

Dans certains cas, vous souhaiterez peut-être 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 immédiatement les données du cache du disque local.

Ceci est utile pour les données qui ne doivent être chargées qu'une seule fois et qui ne devraient pas changer fréquemment ou nécessiter une écoute active. Par exemple, l'application de blog des exemples précédents utilise cette méthode pour charger le profil d'un utilisateur lorsqu'il commence à créer un nouveau message :

Web version 9

import { getDatabase, ref, onValue } from "firebase/database";
import { getAuth } from "firebase/auth";

const db = getDatabase();
const auth = getAuth();

const userId = auth.currentUser.uid;
return onValue(ref(db, '/users/' + userId), (snapshot) => {
  const username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
}, {
  onlyOnce: true
});

Web version 8

var userId = firebase.auth().currentUser.uid;
return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => {
  var username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
});

Mise à jour ou suppression de données

Mettre à jour des champs spécifiques

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

Lors de l'appel update() , vous pouvez mettre à jour les valeurs enfants de niveau inférieur en spécifiant un chemin pour la clé. Si les données sont stockées à plusieurs emplacements pour mieux évoluer, vous pouvez mettre à jour toutes les instances de ces données à l'aide de la répartition des données .

Par exemple, une application de blog social peut créer une publication et la mettre à jour simultanément dans le flux d'activités récentes et dans le flux d'activités de l'utilisateur en utilisant un code comme celui-ci :

Web version 9

import { getDatabase, ref, child, push, update } from "firebase/database";

function writeNewPost(uid, username, picture, title, body) {
  const db = getDatabase();

  // A post entry.
  const postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  const newPostKey = push(child(ref(db), 'posts')).key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  const updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return update(ref(db), updates);
}

Web version 8

function writeNewPost(uid, username, picture, title, body) {
  // A post entry.
  var postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  var newPostKey = firebase.database().ref().child('posts').push().key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  var updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return firebase.database().ref().update(updates);
}

Cet exemple utilise push() pour créer une publication dans le nœud contenant les publications de tous les utilisateurs sur /posts/$postid et récupérer simultanément la clé. La clé peut ensuite être utilisée pour créer une deuxième entrée dans les messages de l'utilisateur à /user-posts/$userid/$postid .

À l'aide de ces chemins, vous pouvez effectuer des mises à jour simultanées à plusieurs emplacements dans l'arborescence JSON avec un seul appel à update() , comme la façon dont cet exemple crée la nouvelle publication dans les deux emplacements. Les mises à jour simultanées effectuées de cette manière sont atomiques : soit toutes les mises à jour réussissent, soit toutes les mises à jour échouent.

Ajouter un rappel d'achèvement

Si vous souhaitez savoir quand vos données ont été validées, vous pouvez ajouter un rappel d'achèvement. set() et update() prennent tous deux un rappel de complétion facultatif qui est appelé lorsque l'écriture a été validée dans la base de données. Si l'appel a échoué, le rappel reçoit un objet d'erreur indiquant pourquoi l'échec s'est produit.

Web version 9

import { getDatabase, ref, set } from "firebase/database";

const db = getDatabase();
set(ref(db, 'users/' + userId), {
  username: name,
  email: email,
  profile_picture : imageUrl
})
.then(() => {
  // Data saved successfully!
})
.catch((error) => {
  // The write failed...
});

Web version 8

firebase.database().ref('users/' + userId).set({
  username: name,
  email: email,
  profile_picture : imageUrl
}, (error) => {
  if (error) {
    // The write failed...
  } else {
    // Data saved successfully!
  }
});

Suprimmer les données

Le moyen le plus simple de supprimer des données consiste à appeler remove() sur 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 en un seul appel d'API.

Recevez une Promise

Pour savoir quand vos données sont validées sur le serveur Firebase Realtime Database, vous pouvez utiliser un Promise . set() et update() peuvent renvoyer une Promise que vous pouvez utiliser pour savoir quand l'écriture est validée dans la base de données.

Détacher les auditeurs

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

Vous pouvez supprimer un seul écouteur en le passant comme paramètre à off() . L'appel off() à l'emplacement sans argument supprime tous les écouteurs à cet emplacement.

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

Enregistrer les données en tant que transactions

Lorsque vous travaillez avec des données susceptibles d'être corrompues par des modifications simultanées, telles que des compteurs incrémentiels, vous pouvez utiliser une opération de transaction . Vous pouvez attribuer à cette opération une fonction de mise à jour et un rappel de complétion facultatif. La fonction de mise à jour prend l'état actuel des données comme argument et renvoie le nouvel état souhaité que vous souhaitez écrire. Si un autre client écrit à l'emplacement avant que votre nouvelle valeur ne soit écrite avec succès, votre fonction de mise à jour est appelée à nouveau avec la nouvelle valeur actuelle et l'écriture est retentée.

Par exemple, dans l'exemple d'application de blog social, vous pouvez autoriser les utilisateurs à ajouter et supprimer des publications et à suivre le nombre d'étoiles reçues par une publication comme suit :

Web version 9

import { getDatabase, ref, runTransaction } from "firebase/database";

function toggleStar(uid) {
  const db = getDatabase();
  const postRef = ref(db, '/posts/foo-bar-123');

  runTransaction(postRef, (post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

Web version 8

function toggleStar(postRef, uid) {
  postRef.transaction((post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

L'utilisation d'une transaction empêche le nombre d'étoiles d'être incorrect si plusieurs utilisateurs mettent en vedette le même message en même temps ou si le client avait des données obsolètes. Si la transaction est rejetée, le serveur renvoie la valeur actuelle au client, qui exécute à nouveau la transaction avec la valeur mise à jour. Cela se répète jusqu'à ce que la transaction soit acceptée ou que vous abandonniez la transaction.

Incréments côté serveur atomique

Dans le cas d'utilisation ci-dessus, nous écrivons deux valeurs dans la base de données : l'identifiant de l'utilisateur qui met en vedette/non en étoile la publication et le nombre d'étoiles incrémenté. Si nous savons déjà que l'utilisateur met en vedette la publication, nous pouvons utiliser une opération d'incrémentation atomique au lieu d'une transaction.

Web version 9

function addStar(uid, key) {
  import { getDatabase, increment, ref, update } from "firebase/database";
  const dbRef = ref(getDatabase());

  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = increment(1);
  update(dbRef, updates);
}

Web version 8

function addStar(uid, key) {
  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  firebase.database().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. Cependant, étant donné que 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 des conflits spécifiques à une application, tels qu'un utilisateur mettant en vedette une publication qu'il a déjà mise en vedette auparavant, vous devez écrire des règles de sécurité personnalisées pour ce cas d'utilisation.

Travailler avec des données hors ligne

Si un client perd sa connexion réseau, votre application continuera à fonctionner correctement.

Chaque client connecté à une base de données Firebase conserve sa propre version interne de toutes les données actives. Lorsque des données sont écrites, elles sont d'abord écrites 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 reste réactive quelle que soit la latence du réseau ou la connectivité.

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

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

Prochaines étapes