Daten im Web lesen und schreiben

Optional: Prototyp erstellen und mit Firebase Local Emulator Suite testen

Bevor wir darüber sprechen, wie deine App Daten aus Realtime Database liest und schreibt, möchten wir dir einige Tools vorstellen, mit denen du Prototypen erstellen und die Realtime Database Funktionalität testen kannst: Firebase Local Emulator Suite. Wenn Sie verschiedene Datenmodelle ausprobieren, Ihre Sicherheitsregeln optimieren oder die kostengünstigste Möglichkeit zur Interaktion mit dem Back-End finden möchten, kann es sehr hilfreich sein, lokal arbeiten zu können, ohne Live-Dienste bereitzustellen.

Ein Realtime Database-Emulator ist Teil der Local Emulator Suite. Damit kann die Anwendung mit dem Inhalt und der Konfiguration der emulierten Datenbank sowie optional mit den emulierten Projektressourcen (Funktionen, anderen Datenbanken und Sicherheitsregeln) interagieren.

Zur Verwendung des Realtime Database-Emulators sind nur wenige Schritte erforderlich:

  1. Fügen Sie der Testkonfiguration Ihrer App eine Codezeile hinzu, um eine Verbindung zum Emulator herzustellen.
  2. Führen Sie im Stammverzeichnis Ihres lokalen Projektverzeichnisses firebase emulators:start aus.
  3. Sie können wie gewohnt über ein Realtime Database-Plattform-SDK oder über die Realtime Database REST API Aufrufe aus dem Prototypcode Ihrer App starten.

Eine detaillierte Schritt-für-Schritt-Anleitung zu Realtime Database und Cloud Functions findest du. Sehen Sie sich auch die Local Emulator SuiteEinführung 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 Datenabrufs sowie das Sortieren und Filtern von Firebase-Daten behandelt.

Firebase-Daten werden abgerufen, indem Sie einen asynchronen Listener zu firebase.database.Reference anfügen. Der Listener wird einmal für den anfänglichen Status der Daten und dann bei jeder Änderung der Daten ausgelöst.

Grundlegende Schreibvorgänge

Bei einfachen Schreibvorgängen können Sie mit set() Daten in einer bestimmten Referenz speichern und alle vorhandenen Daten an diesem Pfad ersetzen. 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
  });
}

Mit set() werden Daten am angegebenen Speicherort überschrieben, einschließlich aller untergeordneten Knoten.

Daten lesen

Auf Wertänderungen warten

Wenn Sie Daten an einem Pfad lesen und auf Änderungen warten möchten, verwenden Sie onValue(), um Ereignisse zu beobachten. Sie können dieses Ereignis verwenden, um statische Snapshots des Inhalts unter einem bestimmten Pfad zu 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. Dem 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.

Das folgende Beispiel zeigt, wie eine Social-Blogging-Anwendung 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 eine snapshot, die die Daten zum Zeitpunkt des Ereignisses am angegebenen Speicherort in der Datenbank enthält. Sie können die Daten in der snapshot mit der Methode val() abrufen.

Daten einmal lesen

Daten einmal mit get() lesen

Das SDK wurde entwickelt, um Interaktionen mit Datenbankservern zu verwalten, unabhängig davon, ob Ihre App online oder offline ist.

Im Allgemeinen sollten Sie die oben beschriebenen Methoden für Wert-Ereignisse verwenden, um Daten zu lesen und über Aktualisierungen der Daten aus dem Backend benachrichtigt zu werden. Die Abrufmethoden reduzieren die Nutzung und Abrechnung und sind so optimiert, dass Nutzer beim Wechsel zwischen Online- und Offlinemodus die bestmögliche Leistung erhalten.

Wenn Sie die Daten nur einmal benötigen, können Sie mit get() einen Snapshot der Daten aus der Datenbank abrufen. Wenn get() den Serverwert aus irgendeinem Grund 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, die wie oben gezeigt durch die Verwendung eines Echtzeit-Listeners verhindert werden können.

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 vielleicht, 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 die Daten mit once() sofort aus dem lokalen Festplatten-Cache abrufen.

Das ist nützlich für Daten, die nur einmal geladen werden müssen und sich voraussichtlich nicht häufig ändern oder für die aktives Zuhören erforderlich ist. Die Blogging-App in den vorherigen Beispielen verwendet diese Methode beispielsweise, um das Profil eines Nutzers zu laden, wenn er mit dem Erstellen eines neuen Beitrags beginnt:

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

Verwenden Sie die Methode update(), um gleichzeitig in bestimmte untergeordnete Knoten eines Knotens zu schreiben, ohne andere untergeordnete Knoten zu überschreiben.

Wenn Sie update() aufrufen, können Sie untergeordnete Werte auf niedrigerer Ebene aktualisieren, indem Sie einen Pfad für den Schlüssel angeben. Wenn Daten zur besseren Skalierung an mehreren Standorten gespeichert sind, können Sie alle Instanzen dieser Daten mithilfe des Daten-Fan-outs aktualisieren.

In einer App für soziales Bloggen kann beispielsweise ein Beitrag erstellt und gleichzeitig im Feed der letzten Aktivitäten und im Feed der Aktivitäten des Nutzers, der den Beitrag gepostet hat, aktualisiert werden. Dazu wird Code wie dieser verwendet:

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 mit Beiträgen für alle Nutzer unter /posts/$postid zu erstellen und den Schlüssel gleichzeitig abzurufen. Mit dem Schlüssel kann dann ein zweiter Eintrag in den Beiträgen des Nutzers unter /user-posts/$userid/$postid erstellt werden.

Mithilfe dieser Pfade können Sie mit einem einzigen Aufruf von update() gleichzeitig mehrere Speicherorte in der JSON-Baumstruktur aktualisieren, so wie in diesem Beispiel der neue Beitrag an beiden Orten erstellt wird. Gleichzeitige Aktualisierungen, die auf diese Weise erfolgen, sind atomar: Entweder sind alle Aktualisierungen erfolgreich oder alle schlagen fehl.

Abschluss-Callback hinzufügen

Wenn Sie wissen möchten, wann ein Commit für Ihre Daten durchgeführt wurde, können Sie einen Abschluss-Callback hinzufügen. Sowohl set() als auch update() akzeptieren einen optionalen Abschluss-Callback, der aufgerufen wird, wenn die Daten in der Datenbank verbindlich gespeichert wurden. Wenn der Aufruf fehlgeschlagen ist, wird dem Callback ein Fehlerobjekt übergeben, das angibt, warum der Fehler aufgetreten ist.

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

Die einfachste Möglichkeit, Daten zu löschen, besteht darin, remove() für einen Verweis auf den Speicherort dieser Daten aufzurufen.

Zum Löschen können Sie auch 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 per Commit an den Firebase Realtime Database-Server gesendet werden, können Sie einen Promise verwenden. Sowohl set() als auch update() können eine Promise zurückgeben, anhand derer Sie erkennen können, wann die Schreibvorgänge in der Datenbank verbindlich werden.

Listener trennen

Callbacks werden entfernt, indem Sie die off()-Methode der Firebase-Datenbankreferenz aufrufen.

Sie können einen einzelnen Listener entfernen, indem Sie ihn als Parameter an off() übergeben. Wenn Sie off() für den Standort ohne Argumente aufrufen, werden alle Listener an diesem Speicherort entfernt.

Wenn off() auf einen übergeordneten Listener angewendet wird, werden die bei den untergeordneten Knoten registrierten Listener nicht automatisch entfernt. off() muss auch auf alle untergeordneten Listener angewendet werden, um den Rückruf 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 geben. Die Update-Funktion nimmt 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 an den Speicherort schreibt, bevor der neue Wert erfolgreich geschrieben wurde, wird die Aktualisierungsfunktion noch einmal mit dem neuen aktuellen Wert aufgerufen und der Schreibvorgang wird noch einmal versucht.

In der Beispiel-Blogging-App für soziale Netzwerke könnten Sie beispielsweise Nutzern erlauben, Beiträge zu markieren und die Markierung aufzuheben. Außerdem können Sie so verfolgen, wie viele Sterne ein Beitrag erhalten hat:

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;
  });
}

Mit einer Transaktion wird verhindert, dass die Anzahl der Sterne falsch ist, wenn mehrere Nutzer denselben Beitrag gleichzeitig markieren oder der Client veraltete Daten hat. Wenn die Transaktion abgelehnt wird, gibt der Server den aktuellen Wert an den Client zurück, der die Transaktion mit dem aktualisierten Wert noch einmal ausführt. Dieser Vorgang 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 Sternsymbol markiert bzw. dessen Markierung entfernt, und der erhöhten Anzahl von Sternen. Wenn wir bereits wissen, dass der Nutzer den Beitrag mit einem Stern markiert, können wir anstelle einer Transaktion einen atomaren Inkrementierungsvorgang 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);
}

Dieser Code verwendet keinen Transaktionsvorgang. Daher wird er bei einem Updatekonflikt nicht automatisch noch einmal ausgeführt. Da die Erhöhung 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 zuvor markiert hat, sollten Sie benutzerdefinierte Sicherheitsregeln für diesen Anwendungsfall schreiben.

Offline mit Daten arbeiten

Wenn ein Client die Netzwerkverbindung verliert, funktioniert Ihre Anwendung weiterhin ordnungsgemäß.

Jeder Client, der mit einer Firebase-Datenbank verbunden ist, verwaltet seine eigenen internen Versionen aller aktiven Daten. Beim Schreiben von Daten werden sie zuerst in diese lokale Version geschrieben. Der Firebase-Client synchronisiert diese Daten dann auf Best-Effort-Basis mit den Remote-Datenbankservern und anderen Clients.

Deshalb lösen alle Schreibvorgänge in die Datenbank lokale Ereignisse aus, bevor Daten auf den Server geschrieben werden. So bleibt Ihre App unabhängig von der Netzwerklatenz oder der Verbindung reaktionsschnell.

Sobald die Verbindung wiederhergestellt ist, empfängt Ihre Anwendung die entsprechenden Ereignisse, sodass der Client mit dem aktuellen Serverstatus synchronisiert, ohne benutzerdefinierten Code schreiben zu müssen.

Weitere Informationen zum Offlineverhalten finden Sie unter Weitere Informationen zu Online- und Offlinefunktionen.

Nächste Schritte