(Optional) Mit der Firebase Emulator Suite Prototypen erstellen und testen
Bevor Sie darüber sprechen, wie Ihre App in Realtime Database Daten liest und in diese schreibt, stellen wir eine Reihe von Tools vor, mit denen Sie Echtzeitdatenbanken erstellen und testen können. Firebase 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 Emulator Suite. ermöglicht es Ihrer App, mit dem Inhalt und der Konfiguration Ihrer emulierten Datenbank zu interagieren, sowie optional Ihre emulierten Projektressourcen (Funktionen, andere Datenbanken, und Sicherheitsregeln).emulator_suite_short
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:start
aus. - 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 Anleitung für Realtime Database und Cloud Functions ist verfügbar. Sehen Sie sich auch die Einführung in die Emulator Suite an.
Datenbankreferenz abrufen
Wenn Sie Daten aus der Datenbank lesen oder schreiben möchten, benötigen Sie eine Instanz von DatabaseReference
:
DatabaseReference ref = FirebaseDatabase.instance.ref();
Daten schreiben
In diesem Dokument werden die Grundlagen zum Lesen und Schreiben von Firebase-Daten behandelt.
Firebase-Daten werden in eine DatabaseReference
geschrieben und von
das Warten auf Ereignisse oder das Warten auf Ereignisse, die von der Referenz ausgegeben werden. Ereignisse werden ausgegeben
einmal für den Anfangszustand der Daten und jedes Mal, wenn sich die Daten ändern.
Grundlegende Schreibvorgänge
Für einfache Schreibvorgänge können Sie set()
verwenden, um Daten in einem bestimmten
referenzieren, wodurch alle unter diesem Pfad vorhandenen Daten ersetzt werden. Sie können eine Referenz auf die folgenden Typen festlegen: String
, boolean
, int
, double
, Map
und List
.
So können Sie beispielsweise einen Nutzer mit set()
hinzufügen:
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
await ref.set({
"name": "John",
"age": 18,
"address": {
"line1": "100 Mountain View"
}
});
Wenn Sie set()
auf diese Weise verwenden, werden Daten am angegebenen Speicherort überschrieben.
einschließlich aller untergeordneten Knoten. Sie können ein untergeordnetes Konto jedoch auch ohne
das gesamte Objekt umschreiben. Wenn Sie Nutzern erlauben möchten, ihre Profile zu aktualisieren
könnten Sie den Nutzernamen wie folgt aktualisieren:
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
// Only update the age, leave the name and address!
await ref.update({
"age": 19,
});
Die update()
-Methode akzeptiert einen untergeordneten Pfad zu Knoten, sodass Sie mehrere Knoten in der Datenbank gleichzeitig aktualisieren können:
DatabaseReference ref = FirebaseDatabase.instance.ref("users");
await ref.update({
"123/age": 19,
"123/address/line1": "1 Mountain View",
});
Daten lesen
Daten lesen, indem auf Wert-Ereignisse gewartet wird
Wenn Sie Daten unter einem Pfad lesen und auf Änderungen warten möchten, verwenden Sie die Eigenschaft onValue
von DatabaseReference
, um auf DatabaseEvent
s zu warten.
Mit der DatabaseEvent
können Sie die Daten an einem bestimmten Pfad lesen, wie sie zum Zeitpunkt des Ereignisses vorhanden sind. Dieses Ereignis wird einmal ausgelöst, wenn der Listener angehängt wird, und dann jedes Mal, wenn sich die Daten, einschließlich aller untergeordneten Elemente, ändern. Das Ereignis hat eine snapshot
-Property, die alle Daten an diesem Speicherort enthält, einschließlich untergeordneter Daten. Wenn keine Daten vorhanden sind, wird der Snapshot
Die Eigenschaft exists
erhält den Wert false
und die Eigenschaft value
ist null.
Im folgenden Beispiel wird eine Social-Blogging-Anwendung veranschaulicht, die die Details eines Beitrags aus der Datenbank abruft:
DatabaseReference starCountRef =
FirebaseDatabase.instance.ref('posts/$postId/starCount');
starCountRef.onValue.listen((DatabaseEvent event) {
final data = event.snapshot.value;
updateStarCount(data);
});
Der Listener empfängt eine DataSnapshot
, die die Daten an der angegebenen Stelle in der Datenbank zum Zeitpunkt des Ereignisses in der Property value
enthält.
Daten einmal lesen
Einmal mit get() lesen
Das SDK wurde entwickelt, um Interaktionen mit Datenbankservern zu verwalten, unabhängig davon, ob Ihre App online oder offline ist.
Generell sollten Sie die oben beschriebenen Wertereignisse verwenden, um um über Aktualisierungen der Daten vom Back-End informiert zu werden. Diese Methoden reduzieren die Nutzung und Abrechnung und sind so optimiert, dass Ihre Nutzer online und offline die bestmögliche Erfahrung haben.
Wenn Sie die Daten nur einmal benötigen, können Sie mit get()
einen Snapshot des
Daten aus der Datenbank. Wenn get()
aus irgendeinem Grund den Serverwert nicht zurückgeben kann, sucht der Client im Cache des lokalen Speichers und gibt einen Fehler zurück, wenn der Wert immer noch nicht gefunden wird.
Das folgende Beispiel zeigt, wie der öffentliche Nutzername eines Nutzers abgerufen wird einmal aus der Datenbank:
final ref = FirebaseDatabase.instance.ref();
final snapshot = await ref.child('users/$userId').get();
if (snapshot.exists) {
print(snapshot.value);
} else {
print('No data available.');
}
Die unnötige Verwendung von get()
kann die Bandbreitennutzung erhöhen und zu Leistungseinbußen führen. Dies kann durch die Verwendung eines Echtzeit-Listeners wie oben gezeigt verhindert werden.
Daten einmal mitOnce() lesen
In einigen Fällen möchten Sie vielleicht, dass der Wert aus dem lokalen Cache 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 Laufwerkcache abzurufen.
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. Zum Beispiel hat die Blog-App in den vorherigen Beispielen, wird diese Methode verwendet, um das Profil eines Nutzers zu laden, mit dem Verfassen eines neuen Beitrags beginnen:
final event = await ref.once(DatabaseEventType.value);
final username = event.snapshot.value?.username ?? 'Anonymous';
Daten aktualisieren oder löschen
Bestimmte Felder aktualisieren
Wenn Sie gleichzeitig in bestimmte untergeordnete Elemente 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 der unteren Ebene aktualisieren, indem Sie einen Pfad für den Schlüssel angeben. Wenn Daten bedarfsgerecht an mehreren Standorten gespeichert sind
können Sie alle Instanzen dieser Daten
Daten-Fan-out. Beispiel:
eine Social-Blogging-App, die einen Beitrag erstellen und gleichzeitig
den Feed „Letzte Aktivität“ und den Aktivitätsfeed des Nutzers, von dem der Beitrag stammt. Dazu verwendet die Blogging-Anwendung Code wie diesen:
void writeNewPost(String uid, String username, String picture, String title,
String body) async {
// A post entry.
final postData = {
'author': username,
'uid': uid,
'body': body,
'title': title,
'starCount': 0,
'authorPic': picture,
};
// Get a key for a new Post.
final newPostKey =
FirebaseDatabase.instance.ref().child('posts').push().key;
// Write the new post's data simultaneously in the posts list and the
// user's post list.
final Map<String, Map> updates = {};
updates['/posts/$newPostKey'] = postData;
updates['/user-posts/$uid/$newPostKey'] = postData;
return FirebaseDatabase.instance.ref().update(updates);
}
In diesem Beispiel wird mit push()
ein Beitrag im Knoten erstellt, der Beiträge für alle Nutzer unter /posts/$postid
enthält, und gleichzeitig wird der Schlüssel mit key
abgerufen. 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 gleichzeitige Aktualisierungen für mehrere Standorte in
JSON-Baum mit einem einzelnen Aufruf von update()
an, wie in diesem Beispiel
erstellt den neuen Beitrag an beiden Orten. Simultane Aktualisierungen auf diese Weise 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
Callbacks zur Vervollständigung. Sowohl set()
als auch update()
geben Future
-Werte zurück, an die
können Sie Erfolgs- und Fehler-Callbacks anhängen, die aufgerufen werden, wenn der Schreibvorgang
an die Datenbank übergeben wurde
und der Aufruf fehlgeschlagen ist.
FirebaseDatabase.instance
.ref('users/$userId/email')
.set(emailAddress)
.then((_) {
// Data saved successfully!
})
.catchError((error) {
// The write failed...
});
Daten löschen
Am einfachsten löschen Sie Daten, indem Sie remove()
auf eine Referenz zum Speicherort dieser Daten anwenden.
Sie können die Daten auch löschen, indem Sie null als Wert für einen anderen Schreibvorgang angeben.
wie set()
oder update()
. Mit dieser Methode und update()
können Sie mehrere untergeordnete Elemente in einem einzigen API-Aufruf löschen.
Daten als Transaktionen speichern
Wenn Sie mit Daten arbeiten, die durch gleichzeitige Änderungen beschädigt werden könnten,
wie inkrementelle Zähler, können Sie eine Transaktion verwenden, indem Sie
Transaktions-Handler für runTransaction()
. Ein Transaktionshandler 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 wiederholt.
In der Beispiel-Blogging-App für soziale Netzwerke könnten die Nutzer beispielsweise festlegen, und die Markierung von Beiträgen aufheben und verfolgen, wie viele Sterne ein Beitrag erhalten hat:
void toggleStar(String uid) async {
DatabaseReference postRef =
FirebaseDatabase.instance.ref("posts/foo-bar-123");
TransactionResult result = await postRef.runTransaction((Object? post) {
// Ensure a post at the ref exists.
if (post == null) {
return Transaction.abort();
}
Map<String, dynamic> _post = Map<String, dynamic>.from(post as Map);
if (_post["stars"] is Map && _post["stars"][uid] != null) {
_post["starCount"] = (_post["starCount"] ?? 1) - 1;
_post["stars"][uid] = null;
} else {
_post["starCount"] = (_post["starCount"] ?? 0) + 1;
if (!_post.containsKey("stars")) {
_post["stars"] = {};
}
_post["stars"][uid] = true;
}
// Return the new data.
return Transaction.success(_post);
});
}
Standardmäßig werden Ereignisse jedes Mal ausgelöst, wenn die Funktion zum Aktualisieren von Transaktionen ausgeführt wird. Wenn Sie die Funktion also mehrmals ausführen, sehen Sie möglicherweise Zwischenstatus.
Sie können applyLocally
auf false
setzen, um diese Zwischenzustände zu unterdrücken und
Warten Sie stattdessen, bis die Transaktion abgeschlossen ist, bevor Ereignisse ausgelöst werden:
await ref.runTransaction((Object? post) {
// ...
}, applyLocally: false);
Das Ergebnis einer Transaktion ist eine TransactionResult
, die Informationen enthält, z. B. ob die Transaktion bestätigt wurde, und den neuen Snapshot:
DatabaseReference ref = FirebaseDatabase.instance.ref("posts/123");
TransactionResult result = await ref.runTransaction((Object? post) {
// ...
});
print('Committed? ${result.committed}'); // true / false
print('Snapshot? ${result.snapshot}'); // DataSnapshot
Transaktion wird storniert
Wenn Sie eine Transaktion sicher abbrechen möchten, rufen Sie Transaction.abort()
auf, um eine AbortTransactionException
zu werfen:
TransactionResult result = await ref.runTransaction((Object? user) {
if (user !== null) {
return Transaction.abort();
}
// ...
});
print(result.committed); // false
Atomare serverseitige Inkremente
Im obigen Anwendungsfall schreiben wir zwei Werte in die Datenbank: die ID des Nutzers, der dem Beitrag ein bzw. kein Sternchen gibt, und die inkrementierte Anzahl der Sterne. Wenn wir dass der Nutzer den Beitrag markiert, können wir ein atomares Inkrement verwenden, statt einer Transaktion.
void addStar(uid, key) async {
Map<String, Object?> updates = {};
updates["posts/$key/stars/$uid"] = true;
updates["posts/$key/starCount"] = ServerValue.increment(1);
updates["user-posts/$key/stars/$uid"] = true;
updates["user-posts/$key/starCount"] = ServerValue.increment(1);
return FirebaseDatabase.instance.ref().update(updates);
}
Dieser Code verwendet keinen Transaktionsvorgang, daher wird er nicht automatisch erstellt bei einem Update noch einmal ausführen. Da der Inkrementenvorgang jedoch direkt auf dem Datenbankserver ausgeführt wird, besteht keine Gefahr eines Konflikts.
Wenn Sie anwendungsspezifische Konflikte erkennen und ablehnen möchten, z. B. bereits markierte Beiträge markieren, sollten Sie benutzerdefinierte Sicherheitsregeln für diesen Anwendungsfall.
Offline mit Daten arbeiten
Wenn ein Client die Netzwerkverbindung verliert, funktioniert deine App weiterhin korrekt sind.
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 mit der Remotedatenbank. mit anderen Clients auf Best-Effort-Basis zu verstehen.
Daher werden alle Schreibvorgänge in der Datenbank sofort als lokale Ereignisse ausgelöst, bevor Daten auf den Server geschrieben werden. Das bedeutet, dass Ihre App und unabhängig von der Netzwerklatenz oder der Konnektivität reaktionsschnell ist.
Sobald die Verbindung wiederhergestellt ist, erhält Ihre App die entsprechenden um eine Synchronisierung mit dem aktuellen Serverstatus zu ermöglichen, benutzerdefinierten Code schreiben.
Wir werden später noch näher auf das Offlineverhalten eingehen, Weitere Informationen zu Online- und Offlinefunktionen