Daten im Web lesen und schreiben

(Optional) Prototyp und Test mit Firebase Local Emulator Suite

Bevor wir darüber sprechen, wie Ihre App aus der Echtzeitdatenbank liest und in sie schreibt, stellen wir eine Reihe von Tools vor, mit denen Sie die Funktionalität von Echtzeitdatenbanken testen und testen können: Firebase Local Emulator Suite. Wenn Sie verschiedene Datenmodelle ausprobieren, Ihre Sicherheitsregeln optimieren oder die kostengünstigste Möglichkeit zur Interaktion mit dem Back-End suchen, kann es eine gute Idee sein, lokal zu arbeiten, ohne Live-Dienste bereitzustellen.

Ein Echtzeit-Datenbank-Emulator ist Teil der Local Emulator Suite, die es Ihrer App ermöglicht, mit Ihrem emulierten Datenbankinhalt und Ihrer Konfiguration sowie optional mit Ihren emulierten Projektressourcen (Funktionen, andere Datenbanken und Sicherheitsregeln) zu interagieren.

Die Verwendung des Echtzeit-Datenbank-Emulators umfasst nur wenige Schritte:

  1. Hinzufügen einer Codezeile zur Testkonfiguration Ihrer App, um eine Verbindung zum Emulator herzustellen.
  2. Führen Sie im Stammverzeichnis Ihres lokalen Projektverzeichnisses firebase emulators:start .
  3. Tätigen von Aufrufen aus dem Prototypcode Ihrer App wie gewohnt mit einem Realtime Database Platform SDK oder mit der Realtime Database REST API.

Eine ausführliche exemplarische Vorgehensweise zu Realtime Database und Cloud Functions ist verfügbar. Sehen Sie sich auch die Einführung zur Local Emulator Suite an .

Holen Sie sich eine Datenbankreferenz

Um Daten aus der Datenbank zu lesen oder zu schreiben, benötigen Sie eine Instanz von firebase.database.Reference :

Web v8

var database = firebase.database();

Web v9

import { getDatabase } from "firebase/database";

const database = getDatabase();

Daten schreiben

In diesem Dokument werden die Grundlagen des Abrufens von Daten sowie das Ordnen und Filtern von Firebase-Daten behandelt.

Firebase-Daten werden abgerufen, indem ein asynchroner Listener an eine firebase.database.Reference . Der Listener wird einmal für den Anfangszustand der Daten und jedes Mal, wenn sich die Daten ändern, getriggert.

Grundlegende Schreibvorgänge

Für grundlegende Schreibvorgänge können Sie set() , um Daten in einer angegebenen Referenz zu speichern und alle vorhandenen Daten in diesem Pfad zu ersetzen. Zum Beispiel könnte eine Social-Blogging-Anwendung einen Benutzer mit set() wie folgt hinzufügen:

Web v8

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

Web v9

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

Die Verwendung von set() überschreibt Daten an der angegebenen Position, einschließlich aller untergeordneten Knoten.

Daten lesen

Auf Wertereignisse achten

Um Daten an einem Pfad zu lesen und auf Änderungen zu warten, verwenden Sie die Methoden on() oder once() von firebase.database.Reference , um Ereignisse zu beobachten.

Veranstaltung Typische Verwendung
value Lesen und hören Sie auf Änderungen am gesamten Inhalt eines Pfads.

Sie können das value Ereignis verwenden, um einen statischen Snapshot des Inhalts eines bestimmten Pfads zu lesen, wie er zum Zeitpunkt des Ereignisses vorhanden war. Diese Methode wird einmal ausgelöst, wenn der Listener angehängt wird und jedes Mal, wenn sich die Daten, einschließlich der untergeordneten Elemente, ändern. Dem Ereignisrückruf wird ein Snapshot übergeben, der alle Daten an diesem Speicherort enthält, einschließlich der untergeordneten Daten. Wenn keine Daten vorhanden sind, gibt der Snapshot false wenn Sie exists() aufrufen exists() und null wenn Sie val() darauf aufrufen.

Das folgende Beispiel zeigt eine Social-Blogging-Anwendung, die die Sternzahl eines Beitrags aus der Datenbank abruft:

Web v8

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

Web v9

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

Der Listener empfängt einen snapshot , der die Daten an der angegebenen Stelle in der Datenbank zum Zeitpunkt des Ereignisses enthält. Sie können die Daten im 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, um über Aktualisierungen der Daten vom Back-End benachrichtigt zu werden. Die Listener-Techniken reduzieren Ihre Nutzung und Abrechnung und sind optimiert, um Ihren Benutzern die beste Erfahrung zu 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 get() nicht zurückgeben kann, untersucht 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 Nutzung der Bandbreite erhöhen und zu Leistungsverlusten führen, die durch die Verwendung eines Echtzeit-Listeners wie oben gezeigt verhindert werden können.

Web v8

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

Web v9

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

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() , um die Daten sofort aus dem lokalen Festplatten-Cache zu holen.

Dies ist nützlich für Daten, die nur einmal geladen werden müssen und sich nicht häufig ändern oder eine aktive Überwachung erfordern. Die Blogging-App in den vorherigen Beispielen verwendet beispielsweise diese Methode, um das Profil eines Benutzers zu laden, wenn er mit dem Verfassen eines neuen Beitrags beginnt:

Web v8

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

Web v9

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

Aktualisieren oder Löschen von Daten

Spezifische Felder aktualisieren

Um gleichzeitig in bestimmte untergeordnete Knoten eines Knotens zu schreiben, 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-Fanout aktualisieren.

Beispielsweise könnte eine Social-Blogging-App einen Beitrag erstellen und ihn gleichzeitig mit dem folgenden Code auf den Feed der letzten Aktivitäten und den Aktivitätsfeed des postenden Benutzers aktualisieren:

Web v8

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

Web v9

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

Dieses Beispiel verwendet push() , um einen Beitrag im Knoten mit Beiträgen für alle Benutzer unter /posts/$postid und gleichzeitig den Schlüssel abzurufen. Der Schlüssel kann dann verwendet werden, um einen zweiten Eintrag in den Beiträgen des Benutzers unter /user-posts/$userid/$postid .

Mithilfe dieser Pfade können Sie mit einem einzigen Aufruf von update() gleichzeitige Aktualisierungen an mehreren Speicherorten im JSON-Baum durchführen, z. B. wie in diesem Beispiel der neue Beitrag an beiden Speicherorten erstellt wird. Gleichzeitige Aktualisierungen, die auf diese Weise durchgeführt werden, sind atomar: Entweder sind alle Aktualisierungen erfolgreich oder alle Aktualisierungen schlagen fehl.

Hinzufügen eines Abschlussrückrufs

Wenn Sie wissen möchten, wann Ihre Daten festgeschrieben wurden, können Sie einen Abschlussrückruf hinzufügen. Sowohl set() als auch update() nehmen einen optionalen Abschluss-Callback entgegen, der aufgerufen wird, wenn der Schreibvorgang an die Datenbank übergeben wurde. Wenn der Aufruf nicht erfolgreich war, wird dem Rückruf ein Fehlerobjekt übergeben, das angibt, warum der Fehler aufgetreten ist.

Web v8

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

Web v9

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

Daten löschen

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

Sie können auch löschen, indem Sie null als Wert für einen anderen Schreibvorgang wie set() oder update() . Sie können diese Technik mit update() , um mehrere untergeordnete Elemente in einem einzigen API-Aufruf zu löschen.

Erhalten Sie ein Promise

Um zu wissen, wann Ihre Daten an den Firebase Realtime Database-Server übergeben werden, können Sie ein Promise . Sowohl set() als auch update() können ein Promise Sie wissen können, wann der Schreibvorgang an die Datenbank übergeben wird.

Hörer trennen

Rückrufe werden durch Aufrufen der Methode off() in Ihrer Firebase-Datenbankreferenz entfernt.

Sie können einen einzelnen Listener entfernen, indem Sie ihn als Parameter an off() . Das Aufrufen von off() an der Position ohne Argumente entfernt alle Listener an dieser Position.

Das Aufrufen von off() auf einem übergeordneten Listener entfernt nicht automatisch Listener, die auf seinen untergeordneten Knoten registriert sind; off() muss auch auf allen untergeordneten Listenern aufgerufen 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 eine Transaktionsoperation verwenden . Sie können dieser Operation eine Update-Funktion und einen optionalen Abschluss-Callback geben. Die Update-Funktion nimmt den aktuellen Zustand der Daten als Argument und gibt den neuen gewünschten Zustand zurück, den Sie schreiben möchten. Wenn ein anderer Client in die Position schreibt, bevor Ihr neuer Wert erfolgreich geschrieben wurde, wird Ihre Update-Funktion erneut mit dem neuen aktuellen Wert aufgerufen und der Schreibvorgang wird wiederholt.

In der Beispiel-App für soziales Bloggen könnten Sie beispielsweise Benutzern erlauben, Beiträge zu markieren und die Markierung aufzuheben und wie folgt zu verfolgen, wie viele Sterne ein Beitrag erhalten hat:

Web v8

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

Web v9

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

Die Verwendung einer Transaktion verhindert, dass die Sternzählung falsch ist, wenn mehrere Benutzer denselben Beitrag gleichzeitig markieren oder der Kunde 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 erneut ausführt. Dies wiederholt sich, 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 Benutzers, der den Beitrag markiert/deaktiviert, und die inkrementierte Sternzahl. Wenn wir bereits wissen, dass der Benutzer den Beitrag markiert, können wir anstelle einer Transaktion eine atomare Inkrementierungsoperation verwenden.

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 keine Transaktionsoperation, daher wird er nicht automatisch erneut ausgeführt, wenn ein Update widersprüchlich ist. Da die Inkrementierungsoperation 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 Benutzer 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 seine 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 dann diese Daten mit den Remote-Datenbankservern und mit anderen Clients nach dem „Best-Effort“.

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 Netzwerklatenz oder Konnektivität reaktionsschnell bleibt.

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

Weitere Informationen zum Offline-Verhalten finden Sie unter Weitere Informationen zu Online- und Offline-Funktionen ..

Nächste Schritte