(Optional) Prototyping und Testen mit Firebase Local Emulator Suite
Bevor wir darüber sprechen, wie Ihre App Daten aus liest und in Realtime Database schreibt, stellen wir Ihnen eine Reihe von Tools vor, mit denen Sie Realtime Database -Funktionen prototypisieren und testen können: Firebase Local Emulator Suite. Wenn Sie verschiedene Datenmodelle ausprobieren, Ihre Sicherheitsregeln optimieren oder die kostengünstigste Möglichkeit für die Interaktion mit dem Back-End finden möchten, kann es eine gute Idee sein, lokal zu arbeiten, ohne Live-Dienste bereitzustellen.
Ein Realtime Database-Emulator ist Teil der Local Emulator Suite. Damit kann Ihre App mit den emulierten Datenbankinhalten und der emulierten Konfiguration sowie optional mit den emulierten Projektressourcen (Funktionen, andere Datenbanken und Sicherheitsregeln) interagieren.
Die Verwendung des Realtime Database-Emulators umfasst nur wenige Schritte:
- Fügen Sie der Testkonfiguration Ihrer App eine Codezeile hinzu, um eine Verbindung zum Emulator herzustellen.
- Führen Sie im Stammverzeichnis Ihres lokalen Projektverzeichnisses
firebase emulators:startaus. - Aufrufe aus dem Prototypcode Ihrer App mit einem Realtime Database-Plattform-SDK wie gewohnt oder mit der Realtime Database REST API ausführen.
Eine detaillierte Anleitung mit Realtime Database und Cloud Functions ist verfügbar. Sehen Sie sich auch die Einführung in Local Emulator Suite an.
Datenbankreferenz abrufen
Zum Lesen oder Schreiben von Daten aus der Datenbank benötigen Sie eine Instanz von firebase.database.Reference:
Web
import { getDatabase } from "firebase/database"; const database = getDatabase();
Web
var database = firebase.database();
Daten schreiben
In diesem Dokument werden die Grundlagen des Abrufens von Daten sowie das Sortieren und Filtern von Firebase-Daten behandelt.
Firebase-Daten werden abgerufen, indem ein asynchroner Listener an eine firebase.database.Reference angehängt wird. Der Listener wird einmal für den Anfangszustand der Daten und dann jedes Mal ausgelöst, wenn sich die Daten ändern.
Grundlegende Schreibvorgänge
Für einfache Schreibvorgänge können Sie set() verwenden, um Daten in einer bestimmten Referenz zu speichern. Dabei werden alle vorhandenen Daten in diesem Pfad ersetzt. Eine Social-Blogging-Anwendung könnte beispielsweise einen Nutzer mit set() so hinzufügen:
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 }); }
Wenn Sie set() verwenden, werden Daten am angegebenen Speicherort überschrieben, einschließlich aller untergeordneten Knoten.
Daten lesen
Auf Wertereignisse warten
Wenn Sie Daten in einem Pfad lesen und auf Änderungen reagieren möchten, verwenden Sie onValue(), um Ereignisse zu beobachten. Mit diesem Ereignis können Sie statische Snapshots der Inhalte in einem bestimmten Pfad lesen, wie sie zum Zeitpunkt des Ereignisses vorhanden waren. Diese Methode wird einmal ausgelöst, wenn der Listener angehängt wird, und dann jedes Mal, wenn sich die Daten, einschließlich der untergeordneten Elemente, ändern. An den Ereignis-Callback wird ein Snapshot übergeben, der alle Daten an diesem Speicherort enthält, einschließlich untergeordneter Daten. Wenn keine Daten vorhanden sind, gibt der Snapshot false zurück, wenn Sie exists() aufrufen, und null, wenn Sie val() aufrufen.
Im folgenden Beispiel wird eine Social-Blogging-Anwendung gezeigt, die die Anzahl der Sterne eines Beitrags aus der Datenbank abruft:
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); });
Der Listener empfängt ein snapshot, das die Daten am angegebenen Speicherort in der Datenbank zum Zeitpunkt des Ereignisses enthält. Sie können die Daten in der snapshot mit der Methode val() abrufen.
Daten einmalig lesen
Daten einmalig mit get() lesen
Das SDK ist so konzipiert, dass es Interaktionen mit Datenbankservern verwaltet, unabhängig davon, ob Ihre App online oder offline ist.
Im Allgemeinen sollten Sie die oben beschriebenen Techniken für Wertereignisse verwenden, um Daten zu lesen und über Aktualisierungen der Daten vom Backend benachrichtigt zu werden. Die Listener-Techniken reduzieren die Nutzung und Abrechnung und sind so optimiert, dass sie Ihren Nutzern die bestmögliche Erfahrung bieten, wenn sie online und offline gehen.
Wenn Sie die Daten nur einmal benötigen, können Sie mit get() einen Snapshot der Daten aus der Datenbank abrufen. Wenn get() aus irgendeinem Grund den Serverwert nicht zurückgeben kann, prüft der Client den lokalen Speichercache und gibt einen Fehler zurück, wenn der Wert immer noch nicht gefunden wird.
Die unnötige Verwendung von get() kann die Bandbreitennutzung erhöhen und zu Leistungseinbußen führen. Dies lässt sich durch die Verwendung eines Echtzeit-Listeners wie oben gezeigt verhindern.
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); });
Daten einmal mit einem Beobachter lesen
In einigen Fällen möchten Sie möglicherweise, dass der Wert aus dem lokalen Cache sofort zurückgegeben wird, anstatt auf dem Server nach einem aktualisierten Wert zu suchen. In diesen Fällen können Sie once() verwenden, um die Daten sofort aus dem lokalen Datenträger-Cache abzurufen.
Das ist nützlich für Daten, die nur einmal geladen werden müssen und sich voraussichtlich nicht häufig ändern oder kein aktives Listening erfordern. Die Blogging-App in den vorherigen Beispielen verwendet diese Methode beispielsweise, um das Profil eines Nutzers zu laden, wenn er einen neuen Beitrag verfasst:
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'; // ... });
Daten aktualisieren oder löschen
Bestimmte Felder aktualisieren
Wenn Sie gleichzeitig in bestimmte untergeordnete Knoten eines Knotens schreiben möchten, ohne andere untergeordnete Knoten zu überschreiben, verwenden Sie die Methode update().
Beim Aufrufen von update() können Sie untergeordnete Werte auf niedrigerer Ebene aktualisieren, indem Sie einen Pfad für den Schlüssel angeben. Wenn Daten an mehreren Standorten gespeichert werden, um eine bessere Skalierung zu ermöglichen, können Sie alle Instanzen dieser Daten mithilfe von Daten-Fan-out aktualisieren.
Eine Social-Blogging-App könnte beispielsweise einen Beitrag erstellen und ihn gleichzeitig im Feed für die letzten Aktivitäten und im Aktivitätsfeed des Nutzers, der den Beitrag gepostet hat, mit folgendem Code aktualisieren:
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); }
In diesem Beispiel wird push() verwendet, um einen Beitrag im Knoten zu erstellen, der Beiträge für alle Nutzer unter /posts/$postid enthält, und gleichzeitig den Schlüssel abzurufen. Mit dem Schlüssel kann dann ein zweiter Eintrag in den Beiträgen des Nutzers unter /user-posts/$userid/$postid erstellt werden.
Mit diesen Pfaden können Sie mit einem einzigen Aufruf von update() gleichzeitige Aktualisierungen an mehreren Stellen im JSON-Baum vornehmen. In diesem Beispiel wird der neue Beitrag an beiden Stellen erstellt. Gleichzeitige Aktualisierungen, die auf diese Weise vorgenommen werden, sind atomar: Entweder sind alle Aktualisierungen erfolgreich oder alle schlagen fehl.
Callback für Abschluss hinzufügen
Wenn Sie wissen möchten, wann Ihre Daten übertragen wurden, können Sie einen Abschluss-Callback hinzufügen. Sowohl set() als auch update() verwenden einen optionalen Abschluss-Callback, der aufgerufen wird, wenn der Schreibvorgang in die Datenbank übertragen wurde. Wenn der Aufruf nicht erfolgreich war, wird dem Callback ein Fehlerobjekt übergeben, das den Grund für den Fehler angibt.
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! } });
Daten löschen
Am einfachsten lassen sich Daten löschen, indem Sie remove() für einen Verweis auf den Speicherort dieser Daten aufrufen.
Sie können auch löschen, indem Sie null als Wert für einen anderen Schreibvorgang wie set() oder update() angeben. Sie können diese Technik mit update() verwenden, um mehrere untergeordnete Elemente in einem einzigen API-Aufruf zu löschen.
Promise erhalten
Wenn Sie wissen möchten, wann Ihre Daten auf dem Firebase Realtime Database-Server festgeschrieben werden, können Sie einen Promise verwenden.
Sowohl set() als auch update() können ein Promise zurückgeben, mit dem Sie feststellen können, wann der Schreibvorgang in die Datenbank übertragen wird.
Listener trennen
Callbacks werden entfernt, indem die Methode off() für Ihre Firebase-Datenbankreferenz aufgerufen wird.
Sie können einen einzelnen Listener entfernen, indem Sie ihn als Parameter an off() übergeben.
Wenn Sie off() für den Speicherort ohne Argumente aufrufen, werden alle Listener an diesem Speicherort entfernt.
Wenn Sie off() für einen übergeordneten Listener aufrufen, werden Listener, die für die untergeordneten Knoten registriert sind, nicht automatisch entfernt. off() muss auch für alle untergeordneten Listener aufgerufen werden, um den Callback zu entfernen.
Daten als Transaktionen speichern
Wenn Sie mit Daten arbeiten, die durch gleichzeitige Änderungen beschädigt werden könnten, z. B. inkrementelle Zähler, können Sie einen Transaktionsvorgang verwenden. Sie können diesem Vorgang eine Aktualisierungsfunktion und einen optionalen Abschluss-Callback zuweisen. Die Aktualisierungsfunktion verwendet den aktuellen Status der Daten als Argument und gibt den neuen gewünschten Status zurück, den Sie schreiben möchten. Wenn ein anderer Client in den Speicherort schreibt, bevor Ihr neuer Wert erfolgreich geschrieben wurde, wird Ihre Aktualisierungsfunktion noch einmal mit dem neuen aktuellen Wert aufgerufen und der Schreibvorgang wird wiederholt.
In der Beispiel-App für Social Blogging können Sie Nutzern beispielsweise erlauben, Beiträge mit einem Stern zu markieren und die Markierung wieder aufzuheben. Außerdem können Sie die Anzahl der Sterne, die ein Beitrag erhalten hat, so erfassen:
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; }); }
Durch die Verwendung einer Transaktion wird verhindert, dass die Anzahl der Sterne falsch ist, wenn mehrere Nutzer denselben Beitrag gleichzeitig mit einem Stern markieren oder der Client veraltete Daten hatte. Wenn die Transaktion abgelehnt wird, gibt der Server den aktuellen Wert an den Client zurück, der die Transaktion dann mit dem aktualisierten Wert noch einmal ausführt. Dies wird wiederholt, bis die Transaktion akzeptiert wird oder Sie die Transaktion abbrechen.
Atomare serverseitige Inkremente
Im obigen Anwendungsfall schreiben wir zwei Werte in die Datenbank: die ID des Nutzers, der den Beitrag mit einem Stern markiert oder die Markierung aufhebt, und die erhöhte Anzahl der Sterne. Wenn wir bereits wissen, dass der Nutzer den Beitrag mit einem Stern markiert, können wir anstelle einer Transaktion einen atomaren Inkrementvorgang verwenden.
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); }
In diesem Code wird kein Transaktionsvorgang verwendet. Er wird daher nicht automatisch noch einmal ausgeführt, wenn ein in Konflikt stehendes Update vorliegt. Da der Inkrementierungsvorgang jedoch direkt auf dem Datenbankserver erfolgt, besteht keine Möglichkeit eines Konflikts.
Wenn Sie anwendungsspezifische Konflikte erkennen und ablehnen möchten, z. B. wenn ein Nutzer einen Beitrag markiert, den er bereits markiert hat, sollten Sie benutzerdefinierte Sicherheitsregeln für diesen Anwendungsfall schreiben.
Offline mit Daten arbeiten
Wenn ein Client die Netzwerkverbindung verliert, funktioniert Ihre App weiterhin ordnungsgemäß.
Jeder Client, der mit einer Firebase-Datenbank verbunden ist, verwaltet eine eigene interne Version aller aktiven Daten. Wenn Daten geschrieben werden, werden sie zuerst in diese lokale Version geschrieben. Der Firebase-Client synchronisiert diese Daten dann nach dem Best-Effort-Prinzip mit den Remote-Datenbankservern und mit anderen Clients.
Daher lösen alle Schreibvorgänge in der Datenbank sofort lokale Ereignisse aus, bevor Daten auf den Server geschrieben werden. Das bedeutet, dass Ihre App unabhängig von Netzwerklatenz oder -verbindung reaktionsfähig bleibt.
Sobald die Verbindung wiederhergestellt ist, empfängt Ihre App die entsprechenden Ereignisse, sodass der Client mit dem aktuellen Serverstatus synchronisiert wird, ohne dass Sie benutzerdefinierten Code schreiben müssen.
Weitere Informationen zum Offlineverhalten finden Sie unter Weitere Informationen zu Online- und Offlinefunktionen.