(Facultatif) Prototypage et test avec Firebase Local Emulator Suite
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 prototyper et tester les fonctionnalités Realtime Database: Firebase Local Emulator Suite. 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 Local Emulator Suite, ce qui permet à votre application d'interagir avec le contenu et la configuration de votre base de données émulée, ainsi que, éventuellement, avec vos ressources de projet émulées (fonctions, autres bases de données et règles de sécurité).
Pour utiliser l'émulateur Realtime Database, procédez comme suit:
- Ajoutez une ligne de code à la configuration de test de votre application pour vous connecter à l'émulateur.
- À partir de la racine du répertoire de votre projet local, exécutez
firebase emulators:start
. - Effectuer des appels à partir du code de prototype de votre application à l'aide d'un SDK de plate-forme Realtime Database comme d'habitude ou à l'aide de l'API REST Realtime Database.
Un tutoriel détaillé impliquant Realtime Database et Cloud Functions est disponible. Nous vous conseillons également de consulter la présentation 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
import { getDatabase } from "firebase/database"; const database = getDatabase();
Web
var database = firebase.database();
Écrire des données
Ce document présente les principes de base de la récupération de données, ainsi que la manière de les trier et de les filtrer.
Les données Firebase sont récupérées en attachant un écouteur asynchrone à un objet firebase.database.Reference
. L'écouteur est déclenché 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. Par exemple, une application de bloggage sur les réseaux sociaux peut ajouter un utilisateur avec set()
comme suit:
Web
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
function writeUserData(userId, name, email, imageUrl) { firebase.database().ref('users/' + userId).set({ username: name, email: email, profile_picture : imageUrl }); }
L'utilisation de set()
écrase les données à l'emplacement spécifié, y compris les nœuds enfants.
Lire des données
Écouter les événements de valeur
Pour lire des données à un chemin d'accès 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 à un chemin d'accès donné, tels qu'ils existaient au moment de l'événement. Cette méthode est déclenchée une fois lorsque l'écouteur est associé, puis 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. Si aucune donnée n'est disponible, l'instantané renvoie false
lorsque vous appelez exists()
et null
lorsque vous appelez val()
.
L'exemple suivant montre comment une application de blog social récupère le nombre d'étoiles d'un post à partir de la base de données:
Web
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
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
contenant 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 snapshot
à l'aide de la méthode val()
.
Lire les données une fois
Lire des 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 connexion.
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 et être averti des mises à jour des données à partir du backend. Les techniques d'écouteur réduisent votre utilisation et votre facturation, et sont optimisées pour offrir à vos utilisateurs la meilleure expérience lorsqu'ils se connectent et se déconnectent.
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 la valeur du serveur, le client sonde le cache de stockage local et renvoie une erreur si la valeur n'est toujours pas trouvée.
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.
Web
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
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 des données une fois avec un observateur
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 ce cas, vous pouvez utiliser once()
pour obtenir immédiatement les données du cache de disque local.
Cela est utile pour les données qui ne doivent être chargées qu'une seule fois et qui ne devraient pas changer fréquemment ni nécessiter une écoute active. Par exemple, l'application de bloggage des exemples précédents utilise cette méthode pour charger le profil d'un utilisateur lorsqu'il commence à rédiger un nouvel article:
Web
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
var userId = firebase.auth().currentUser.uid; return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => { var username = (snapshot.val() && snapshot.val().username) || 'Anonymous'; // ... });
Mettre à jour ou supprimer des données
Modifier des champs spécifiques
Pour écrire simultanément dans des enfants spécifiques d'un nœud sans écraser d'autres 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, une application de bloggage sur les réseaux sociaux peut créer un post et le mettre à jour simultanément dans le flux d'activité récent et le flux d'activité de l'utilisateur qui publie le post à l'aide d'un code comme celui-ci:
Web
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
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 un post dans le nœud contenant des posts pour tous les utilisateurs à /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 posts de l'utilisateur à l'emplacement /user-posts/$userid/$postid
.
À l'aide de ces chemins, vous pouvez effectuer des mises à jour simultanées à plusieurs emplacements de l'arborescence JSON avec un seul appel à update()
, comme dans cet exemple qui crée le nouveau post dans les deux emplacements. Les mises à jour simultanées effectuées de cette manière sont atomiques: toutes les mises à jour réussissent ou toutes échouent.
Ajouter un rappel de fin
Si vous souhaitez savoir quand vos données ont été validées, vous pouvez ajouter un rappel de fin. set()
et update()
acceptent un rappel de finalisation facultatif qui est appelé lorsque l'écriture a été validée dans la base de données. Si l'appel échoue, un objet d'erreur indiquant pourquoi l'échec s'est produit est transmis au rappel.
Web
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
firebase.database().ref('users/' + userId).set({ username: name, email: email, profile_picture : imageUrl }, (error) => { if (error) { // The write failed... } else { // Data saved successfully! } });
Supprimer des 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.
Recevoir un 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 tous deux renvoyer un Promise
que vous pouvez utiliser pour savoir quand l'écriture est validée dans la base de données.
Dissocier les écouteurs
Pour supprimer les rappels, appelez la méthode off()
sur votre référence de base de données Firebase.
Vous pouvez supprimer un seul écouteur en le transmettant en tant que paramètre à off()
.
Appeler off()
sur l'emplacement sans arguments supprime tous les écouteurs à cet emplacement.
L'appel de 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 pouvant être corrompues par des modifications simultanées, telles que des compteurs incrémentaux, vous pouvez utiliser une opération de transaction. Vous pouvez attribuer à cette opération une fonction de mise à jour et un rappel de fin facultatif. La fonction update prend l'état actuel des données comme argument et renvoie l'état souhaité que vous souhaitez écrire. Si un autre client écrit à l'emplacement avant que votre nouvelle valeur ne soit écrite, votre fonction de mise à jour est appelée à nouveau avec la nouvelle valeur actuelle, et l'écriture est réessayée.
Par exemple, dans l'exemple d'application de blog social, vous pouvez autoriser les utilisateurs à ajouter et à supprimer des étoiles aux posts, et à suivre le nombre d'étoiles qu'un post a reçues comme suit:
Web
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
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 ajoutent une étoile au même post en même temps ou si le client dispose de 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. Cette opération se répète jusqu'à ce que la transaction soit acceptée ou que vous l'annuliez.
Incréments atomiques côté serveur
Dans le cas d'utilisation ci-dessus, nous écrivons deux valeurs dans la base de données: l'ID de l'utilisateur qui ajoute/supprime une étoile à la publication et le nombre d'étoiles incrémenté. Si nous savons déjà que l'utilisateur ajoute le post à ses favoris, nous pouvons utiliser une opération d'incrément atomique au lieu d'une transaction.
Web
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
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. 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 reste réactive quelle que soit la latence ou la connectivité du réseau.
Une fois la connectivité rétablie, votre application reçoit l'ensemble d'événements approprié pour que le client se synchronise avec l'état actuel du serveur, sans avoir à écrire de code personnalisé.
Nous reviendrons sur le comportement hors connexion dans la section En savoir plus sur les fonctionnalités en ligne et hors connexion.