Daten im Web lesen und schreiben

Optional: Prototyping und Tests mit Firebase Local Emulator Suite

Bevor wir uns ansehen, wie Ihre App Daten aus Realtime Database 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 nach der kostengünstigsten Möglichkeit suchen, mit dem Backend zu interagieren, kann es sinnvoll sein, lokal zu arbeiten, ohne Live-Dienste bereitzustellen.

Ein Realtime Database-Emulator ist Teil der Local Emulator Suite. Dadurch kann Ihre App mit den emulierten Datenbankinhalten und der Konfiguration sowie optional mit den emulierten Projektressourcen (Funktionen, anderen Datenbanken und Sicherheitsregeln) interagieren.

Die Verwendung des Realtime Database-Emulators umfasst nur wenige Schritte:

  1. Fügen Sie der Testkonfiguration Ihrer App eine Codezeile hinzu, um eine Verbindung zum Emulator herzustellen.
  2. Führen Sie firebase emulators:start im Stammverzeichnis Ihres lokalen Projektverzeichnisses aus.
  3. 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 Schritt-für-Schritt-Anleitung für Realtime Database und Cloud Functions ist verfügbar. Sehen Sie sich auch die Einführung in Local Emulator Suite an.

Datenbankreferenz abrufen

Wenn Sie Daten aus der Datenbank lesen oder in die Datenbank schreiben möchten, 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 firebase.database.Reference angehängt wird. Der Listener wird einmal für den anfänglichen Status der Daten und dann bei jeder Änderung der Daten ausgelöst.

Grundlegende Schreibvorgänge

Für einfache Schreibvorgänge können Sie set() verwenden, um Daten an einem angegebenen Verweis zu speichern und alle vorhandenen Daten an diesem Pfad zu ersetzen. In einer Social-Blogging-Anwendung könnte ein Nutzer mit set() beispielsweise so hinzugefügt werden:

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 Wert-Ereignisse warten

Wenn Sie Daten an einem Pfad lesen und auf Änderungen warten möchten, verwenden Sie onValue(), um Ereignisse zu beobachten. Mit diesem Ereignis können Sie statische Snapshots der Inhalte an 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 untergeordneter Elemente, ändern. An den Ereignis-Callback wird ein Snapshot mit allen Daten an diesem Speicherort übergeben, einschließlich untergeordneter Daten. Wenn keine Daten vorhanden sind, wird für den Snapshot false zurückgegeben, wenn Sie exists() aufrufen, und null, wenn Sie val() aufrufen.

Im folgenden Beispiel wird gezeigt, 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 ein snapshot, das die Daten am angegebenen Speicherort in der Datenbank zum Zeitpunkt des Ereignisses enthält. Sie können die Daten in 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 Wertereignistechniken verwenden, um Daten zu lesen und über Aktualisierungen der Daten aus dem Backend benachrichtigt zu werden. Die Listener-Techniken reduzieren die Nutzung und Abrechnung und sind so optimiert, dass Ihre Nutzer beim Wechsel zwischen Online- und Offlinemodus die bestmögliche Erfahrung haben.

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, fragt der Client den lokalen Speicher-Cache ab 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. Das lässt sich durch die Verwendung eines Echtzeit-Listeners wie oben gezeigt vermeiden.

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 Observer lesen

In einigen Fällen soll der Wert aus dem lokalen Cache sofort zurückgegeben werden, 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 Festplatten-Cache abzurufen.

Das ist nützlich für Daten, die nur einmal geladen werden müssen und sich voraussichtlich nicht häufig ändern oder aktives Zuhören 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().

Wenn Sie update() aufrufen, können Sie untergeordnete Werte aktualisieren, indem Sie einen Pfad für den Schlüssel angeben. Wenn Daten zur besseren Skalierung an mehreren Standorten gespeichert werden, können Sie alle Instanzen dieser Daten mit Data 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 erstellt hat, aktualisieren. Dazu könnte sie Code wie diesen verwenden:

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 gleichzeitig den Schlüssel abzurufen. Der Schlüssel kann dann verwendet werden, um einen zweiten Eintrag in den Beiträgen des Nutzers unter /user-posts/$userid/$postid zu erstellen.

Mit diesen Pfaden können Sie mit einem einzigen Aufruf von update() mehrere Orte im JSON-Baum gleichzeitig aktualisieren. Im folgenden Beispiel wird der neue Beitrag an beiden Orten erstellt. Gleichzeitige Aktualisierungen auf diese Weise sind atomar: Entweder sind alle Aktualisierungen erfolgreich oder alle Aktualisierungen schlagen fehl.

Abschluss-Callback hinzufügen

Wenn Sie wissen möchten, wann Ihre Daten übernommen wurden, können Sie einen Completion-Callback hinzufügen. Sowohl set() als auch update() akzeptieren einen optionalen Completion-Callback, der aufgerufen wird, wenn der Schreibvorgang in der Datenbank committet wurde. Wenn der Aufruf nicht erfolgreich war, 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

Am einfachsten löschen Sie Daten, indem Sie remove() für eine Referenz 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 gespeichert werden, können Sie eine Promise verwenden. Sowohl set() als auch update() können einen Promise zurückgeben, mit dem Sie feststellen können, wann der Schreibvorgang in der Datenbank ausgeführt wurde.

Listener trennen

Callbacks werden entfernt, indem Sie die off()-Methode für Ihre 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 Standort 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 geben. 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 an 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önnten Sie Nutzern beispielsweise erlauben, Beiträge mit Sternen zu markieren und die Anzahl der Sterne für einen Beitrag so zu verfolgen:

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 mit dem aktualisierten Wert noch einmal ausführt. Dieser Vorgang wird wiederholt, bis die Transaktion akzeptiert wird oder Sie sie abbrechen.

angezeigt wird.

Atomare serverseitige Steigerungen

Im obigen Anwendungsfall schreiben wir zwei Werte in die Datenbank: die ID des Nutzers, der den Beitrag mit einem Stern markiert oder die Markierung entfernt, und die erhöhte Anzahl der Sterne. Wenn wir bereits wissen, dass der Nutzer den Beitrag mit einem Sternchen 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 also nicht automatisch noch einmal ausgeführt, wenn es zu einem Konflikt kommt. Da der Inkrementierungsvorgang jedoch direkt auf dem Datenbankserver erfolgt, besteht keine Konfliktgefahr.

Wenn Sie anwendungsspezifische Konflikte erkennen und ablehnen möchten, z. B. wenn ein Nutzer einen Beitrag mit einem Stern markiert, den er bereits zuvor mit einem Stern 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 seine 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 auf Best-Effort-Basis mit den Remote-Datenbankservern und anderen Clients.

Daher lösen alle Schreibvorgänge in die Datenbank sofort lokale Ereignisse aus, bevor Daten auf den Server geschrieben werden. Das bedeutet, dass Ihre App unabhängig von der Netzwerklatenz oder der Verbindung reaktionsfähig bleibt.

Sobald die Verbindung wiederhergestellt ist, erhält 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 hier.

Nächste Schritte