(Opcjonalnie) Tworzenie prototypów i testowanie za pomocą Firebase Local Emulator Suite
Zanim omówimy, jak aplikacja odczytuje i zapisuje dane w Realtime Database, przedstawimy zestaw narzędzi, których możesz używać do tworzenia prototypów i testowania funkcji Realtime Database: Firebase Local Emulator Suite. Jeśli testujesz różne modele danych, optymalizujesz reguły zabezpieczeń lub szukasz najbardziej opłacalnego sposobu interakcji z backendem, praca lokalna bez wdrażania usług na żywo może być świetnym pomysłem.
Realtime Database Emulator jest częścią Local Emulator Suite, która umożliwia aplikacji interakcję z emulowaną zawartością bazy danych i konfiguracją, a także opcjonalnie z emulowanymi zasobami projektu (funkcjami, innymi bazami danych i regułami zabezpieczeń).
Korzystanie z Realtime Database emulatora wymaga wykonania tylko kilku czynności:
- Dodanie do konfiguracji testowej aplikacji wiersza kodu, który połączy ją z emulatorem.
- Uruchom
firebase emulators:start
w katalogu głównym projektu lokalnego. - Wykonuj wywołania z kodu prototypu aplikacji za pomocą pakietu Realtime Database SDK platformy lub interfejsu Realtime Database REST API.
Dostępny jest szczegółowy przewodnik dotyczący Realtime Database i Cloud Functions. Zapoznaj się też z Local Emulator Suite wprowadzeniem.
Pobieranie odniesienia do bazy danych
Aby odczytywać lub zapisywać dane w bazie danych, potrzebujesz instancji firebase.database.Reference
:
Web
import { getDatabase } from "firebase/database"; const database = getDatabase();
Web
var database = firebase.database();
Zapisywanie danych
W tym dokumencie znajdziesz podstawowe informacje o pobieraniu danych oraz o sortowaniu i filtrowaniu danych Firebase.
Dane Firebase są pobierane przez dołączenie asynchronicznego odbiornika do firebase.database.Reference
. Odbiornik jest wywoływany raz w przypadku stanu początkowego danych i ponownie za każdym razem, gdy dane się zmienią.
Podstawowe operacje zapisu
W przypadku podstawowych operacji zapisu możesz użyć set()
, aby zapisać dane w określonym odwołaniu, zastępując wszystkie istniejące dane w tej ścieżce. Na przykład aplikacja do blogowania w mediach społecznościowych może dodać użytkownika z set()
w ten sposób:
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 }); }
Użycie set()
powoduje zastąpienie danych w określonej lokalizacji, w tym wszystkich węzłów podrzędnych.
Odczytywanie danych
Nasłuchiwanie zdarzeń wartości
Aby odczytać dane ze ścieżki i nasłuchiwać zmian, użyj onValue()
do obserwowania zdarzeń. Za pomocą tego zdarzenia możesz odczytywać statyczne migawki treści w danym ścieżce, które istniały w momencie wystąpienia zdarzenia. Ta metoda jest wywoływana raz po dołączeniu odbiornika i ponownie za każdym razem, gdy zmienią się dane, w tym dane dotyczące dzieci. Funkcja zwrotna zdarzenia otrzymuje migawkę zawierającą wszystkie dane w tej lokalizacji, w tym dane podrzędne. Jeśli nie ma danych, po wywołaniu funkcji exists()
i val()
w migawce zostanie zwrócona wartość false
i null
.
Poniższy przykład pokazuje, jak aplikacja do blogowania społecznościowego pobiera z bazy danych liczbę gwiazdek przyznanych postowi:
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); });
Odbiorca otrzymuje snapshot
, który zawiera dane z określonej lokalizacji w bazie danych w momencie wystąpienia zdarzenia. Dane możesz pobrać z snapshot
za pomocą metody val()
.
Odczytywanie danych tylko raz
Jednorazowe odczytywanie danych za pomocą metody get()
Pakiet SDK jest przeznaczony do zarządzania interakcjami z serwerami baz danych niezależnie od tego, czy aplikacja jest online czy offline.
Ogólnie rzecz biorąc, do odczytywania danych i otrzymywania powiadomień o aktualizacjach danych z backendu należy używać opisanych powyżej technik zdarzeń wartości. Techniki nasłuchiwania zmniejszają zużycie i koszty oraz są zoptymalizowane pod kątem zapewnienia użytkownikom najlepszych wrażeń podczas przechodzenia w tryb online i offline.
Jeśli potrzebujesz danych tylko raz, możesz użyć get()
, aby uzyskać migawkę danych z bazy danych. Jeśli z jakiegokolwiek powodu get()
nie może zwrócić wartości serwera, klient sprawdzi pamięć podręczną lokalnego magazynu i zwróci błąd, jeśli wartość nadal nie zostanie znaleziona.
Niepotrzebne użycie get()
może zwiększyć wykorzystanie przepustowości i spowodować utratę wydajności. Można temu zapobiec, używając odbiornika w czasie rzeczywistym, jak pokazano powyżej.
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); });
Jednokrotne odczytywanie danych za pomocą obserwatora
W niektórych przypadkach możesz chcieć, aby wartość z pamięci podręcznej była zwracana natychmiast, zamiast sprawdzać zaktualizowaną wartość na serwerze. W takich przypadkach możesz użyć once()
, aby natychmiast pobrać dane z lokalnej pamięci podręcznej dysku.
Jest to przydatne w przypadku danych, które trzeba wczytać tylko raz i które nie powinny się często zmieniać ani wymagać aktywnego nasłuchiwania. Na przykład aplikacja do blogowania z poprzednich przykładów używa tej metody do wczytywania profilu użytkownika, gdy zaczyna on pisać nowy post:
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'; // ... });
Aktualizowanie i usuwanie danych
Aktualizowanie określonych pól
Aby jednocześnie zapisywać dane w określonych węzłach podrzędnych węzła bez zastępowania innych węzłów podrzędnych, użyj metody update()
.
Podczas wywoływania funkcji update()
możesz aktualizować wartości podrzędne niższego poziomu, podając ścieżkę do klucza. Jeśli dane są przechowywane w wielu lokalizacjach, aby zapewnić lepszą skalowalność, możesz zaktualizować wszystkie ich wystąpienia za pomocą rozsyłania danych.
Na przykład aplikacja do blogowania społecznościowego może utworzyć post i jednocześnie zaktualizować go w kanale ostatnich aktywności i w kanale aktywności użytkownika, który opublikował post, za pomocą takiego kodu:
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); }
W tym przykładzie używamy push()
, aby utworzyć post w węźle zawierającym posty wszystkich użytkowników pod adresem /posts/$postid
i jednocześnie pobrać klucz. Klucz może być następnie użyty do utworzenia drugiego wpisu w postach użytkownika na stronie /user-posts/$userid/$postid
.
Korzystając z tych ścieżek, możesz jednocześnie aktualizować wiele lokalizacji w drzewie JSON za pomocą jednego wywołania update()
, np. w tym przykładzie, w którym nowy post jest tworzony w obu lokalizacjach. Jednoczesne aktualizacje przeprowadzane w ten sposób są niepodzielne: wszystkie aktualizacje się udają lub wszystkie się nie udają.
Dodawanie wywołania zwrotnego po zakończeniu
Jeśli chcesz wiedzieć, kiedy dane zostały zatwierdzone, możesz dodać wywołanie zwrotne po zakończeniu. Zarówno set()
, jak i update()
przyjmują opcjonalne wywołanie zwrotne zakończenia, które jest wywoływane, gdy zapis zostanie zatwierdzony w bazie danych. Jeśli wywołanie się nie powiedzie, funkcja zwrotna otrzyma obiekt błędu wskazujący przyczynę niepowodzenia.
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! } });
Usuń dane
Najprostszym sposobem usunięcia danych jest wywołanie funkcji remove()
na odwołaniu do lokalizacji tych danych.
Możesz też usunąć wartość, podając null
w przypadku innej operacji zapisu, np. set()
lub update()
. Możesz użyć tej techniki
z update()
, aby usunąć wiele elementów podrzędnych w ramach jednego wywołania interfejsu API.
Otrzymaj Promise
Aby dowiedzieć się, kiedy dane zostaną zapisane na serwerze Firebase Realtime Database, możesz użyć Promise
.
Zarówno set()
, jak i update()
mogą zwracać Promise
, którego możesz użyć, aby dowiedzieć się, kiedy zapis zostanie zatwierdzony w bazie danych.
Odłączanie detektorów
Wywołania zwrotne są usuwane przez wywołanie metody off()
w odniesieniu do bazy danych Firebase.
Możesz usunąć pojedynczego odbiorcę, przekazując go jako parametr do funkcji off()
.
Wywołanie funkcji off()
w lokalizacji bez argumentów powoduje usunięcie wszystkich słuchaczy w tej lokalizacji.
Wywołanie off()
na odbiorniku rodzica nie powoduje automatycznego usunięcia odbiorników zarejestrowanych w jego węzłach podrzędnych. Aby usunąć wywołanie zwrotne, należy też wywołać off()
na wszystkich odbiornikach podrzędnych.
Zapisywanie danych jako transakcji
W przypadku danych, które mogą zostać uszkodzone przez równoczesne modyfikacje, np. liczników przyrostowych, możesz użyć operacji transakcji. Możesz przypisać do tej operacji funkcję aktualizacji i opcjonalne wywołanie zwrotne po zakończeniu. Funkcja aktualizacji przyjmuje bieżący stan danych jako argument i zwraca nowy pożądany stan, który chcesz zapisać. Jeśli inny klient zapisze dane w tej lokalizacji, zanim nowa wartość zostanie zapisana, funkcja aktualizacji zostanie ponownie wywołana z nową bieżącą wartością, a zapis zostanie ponowiony.
Na przykład w przykładowej aplikacji do blogowania społecznościowego możesz zezwolić użytkownikom na oznaczanie postów gwiazdką i cofanie tego oznaczenia oraz śledzić liczbę gwiazdek, które otrzymał post, w ten sposób:
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; }); }
Użycie transakcji zapobiega nieprawidłowemu zliczaniu gwiazdek, jeśli kilku użytkowników jednocześnie oznaczy ten sam post gwiazdką lub klient ma nieaktualne dane. Jeśli transakcja zostanie odrzucona, serwer zwróci bieżącą wartość do klienta, który ponownie uruchomi transakcję z zaktualizowaną wartością. Powtarzaj tę czynność, aż transakcja zostanie zaakceptowana lub anulowana.
Atomowe zwiększanie wartości po stronie serwera
W tym przypadku zapisujemy w bazie danych 2 wartości: identyfikator użytkownika, który dodaje lub usuwa gwiazdkę, oraz zwiększoną liczbę gwiazdek. Jeśli wiemy, że użytkownik oznaczył posta gwiazdką, możemy użyć operacji przyrostu atomowego zamiast transakcji.
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); }
Ten kod nie korzysta z operacji transakcji, więc nie jest automatycznie uruchamiany ponownie w przypadku sprzecznej aktualizacji. Jednak ponieważ operacja zwiększania odbywa się bezpośrednio na serwerze bazy danych, nie ma możliwości wystąpienia konfliktu.
Jeśli chcesz wykrywać i odrzucać konflikty związane z aplikacją, np. gdy użytkownik oznaczy gwiazdką post, który już wcześniej oznaczył, napisz niestandardowe reguły bezpieczeństwa dla tego przypadku użycia.
Praca z danymi offline
Jeśli klient utraci połączenie z siecią, aplikacja będzie nadal działać prawidłowo.
Każdy klient połączony z bazą danych Firebase ma własną wewnętrzną wersję aktywnych danych. Gdy dane są zapisywane, najpierw trafiają do tej lokalnej wersji. Klient Firebase synchronizuje te dane z serwerami zdalnej bazy danych i innymi klientami w miarę możliwości.
W rezultacie wszystkie zapisy w bazie danych natychmiast wywołują zdarzenia lokalne, zanim jakiekolwiek dane zostaną zapisane na serwerze. Oznacza to, że aplikacja pozostaje responsywna niezależnie od opóźnienia sieci lub połączenia.
Po ponownym nawiązaniu połączenia aplikacja otrzymuje odpowiedni zestaw zdarzeń, dzięki czemu klient synchronizuje się z bieżącym stanem serwera bez konieczności pisania niestandardowego kodu.
Więcej informacji o zachowaniu offline znajdziesz w artykule Więcej informacji o funkcjach online i offline.