(Opcjonalnie) Prototypowanie i testowanie za pomocą Firebase Local Emulator Suite
Zanim omówimy, jak Twoja aplikacja odczytuje i zapisuje dane w Realtime Database, przedstawimy zestaw narzędzi, których możesz używać do prototypowania i testowania funkcji Realtime Database: Firebase Local Emulator Suite. Jeśli testujesz różne modele danych, optymalizujesz reguły bezpieczeństwa lub szukasz najbardziej opłacalnego sposobu interakcji z backendem, praca lokalna bez wdrażania usług na żywo może być dobrym rozwiązaniem.
Emulator Realtime Database jest częścią Local Emulator Suite, który umożliwia aplikacji interakcję z emulowaną zawartością i konfiguracją bazy danych, a także opcjonalnie z emulowanymi zasobami projektu (funkcjami, innymi bazami danych i regułami bezpieczeństwa).
Korzystanie z emulatora Realtime Database wymaga wykonania tylko kilku czynności:
- Dodanie wiersza kodu do konfiguracji testowej aplikacji, aby połączyć się z emulatorem.
- Uruchomienie polecenia
firebase emulators:startw katalogu głównym projektu lokalnego. - Wywoływanie kodu prototypu aplikacji za pomocą pakietu SDK platformy Realtime Database lub interfejsu API REST Realtime Database.
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
Z tego dokumentu dowiesz się, jak pobierać dane oraz jak sortować i filtrować dane Firebase.
Dane Firebase są pobierane przez dołączenie asynchronicznego detektora do firebase.database.Reference. Detektor jest aktywowany 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ć funkcji set(), aby zapisać dane w określonym odniesieniu, zastępując wszystkie istniejące dane w tej ścieżce. Na przykład aplikacja do blogowania społecznościowego może dodać użytkownika za pomocą funkcji 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 funkcji 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 w ścieżce i nasłuchiwać zmian, użyj funkcji onValue() do obserwowania zdarzeń. Za pomocą tego zdarzenia możesz odczytywać statyczne zrzuty zawartości w danej ścieżce, które istniały w momencie zdarzenia. Ta metoda jest wywoływana raz po dołączeniu detektora i ponownie za każdym razem, gdy zmienią się dane, w tym dane podrzędne. Wywołanie zwrotne zdarzenia otrzymuje zrzut zawierający wszystkie dane w tej lokalizacji, w tym dane podrzędne. Jeśli nie ma danych, zrzut zwróci wartość false, gdy wywołasz funkcję exists(), oraz wartość null, gdy wywołasz funkcję val().
Poniższy przykład pokazuje aplikację do blogowania społecznościowego, która pobiera z bazy danych liczbę gwiazdek posta:
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); });
Detektor otrzymuje snapshot, który zawiera dane w określonej lokalizacji w bazie danych w momencie zdarzenia. Dane w snapshot możesz pobrać za pomocą metody val().
Jednorazowe odczytywanie danych
Jednorazowe odczytywanie danych za pomocą funkcji get()
Pakiet SDK jest zaprojektowany do zarządzania interakcjami z serwerami baz danych, niezależnie od tego, czy aplikacja jest online czy offline.
Zasadniczo do odczytywania danych należy używać opisanych powyżej metod zdarzeń wartości, aby otrzymywać powiadomienia o aktualizacjach danych z backendu. Metody detektorów zmniejszają zużycie i opłaty oraz są zoptymalizowane pod kątem zapewnienia użytkownikom najlepszych wrażeń podczas przechodzenia do trybu online i offline.
Jeśli potrzebujesz danych tylko raz, możesz użyć funkcji get(), aby pobrać zrzut danych z bazy danych. Jeśli z jakiegoś powodu funkcja get() nie może zwrócić wartości serwera, klient sprawdzi pamięć podręczną pamięci lokalnej i zwróci błąd, jeśli wartość nadal nie zostanie znaleziona.
Niepotrzebne użycie funkcji get() może zwiększyć zużycie przepustowości i spowodować pogorszenie wydajności. Można temu zapobiec, używając detektora czasu rzeczywistego, 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); });
Jednorazowe 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ć funkcji once(), aby natychmiast pobrać dane z pamięci podręcznej dysku lokalnego.
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 w poprzednich przykładach używa tej metody do wczytywania profilu użytkownika, gdy zaczyna on tworzyć 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 elementach 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 elementów podrzędnych niższego poziomu, określając ścieżkę do klucza. Jeśli dane są przechowywane w wielu lokalizacjach, aby lepiej skalować
, możesz zaktualizować wszystkie instancje tych danych za pomocą
zwielokrotnienia wyjściowego danych.
Na przykład aplikacja do blogowania społecznościowego może utworzyć post i jednocześnie zaktualizować go w kanale ostatnich aktywności oraz w kanale aktywności użytkownika, który go opublikował, 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 funkcji push(), aby utworzyć post w węźle zawierającym posty wszystkich użytkowników w ścieżce /posts/$postid i jednocześnie pobrać klucz. Klucz można następnie użyć do utworzenia drugiego wpisu w postach użytkownika w ścieżce /user-posts/$userid/$postid.
Za pomocą tych ścieżek możesz jednocześnie aktualizować wiele lokalizacji w drzewie JSON za pomocą jednego wywołania funkcji update(), tak jak w tym przykładzie, który tworzy nowy post w obu lokalizacjach. Jednoczesne aktualizacje wykonane w ten sposób są niepodzielne: wszystkie aktualizacje się powiodą lub wszystkie się nie powiodą.
Dodawanie wywołania zwrotnego zakończenia
Jeśli chcesz wiedzieć, kiedy dane zostały zatwierdzone, możesz dodać wywołanie zwrotne zakończenia. Zarówno funkcja 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 powiodło, wywołanie zwrotne otrzymuje 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! } });
Usuwanie danych
Najprostszym sposobem usunięcia danych jest wywołanie funkcji remove() w odniesieniu do lokalizacji tych danych.
Możesz też usunąć dane, określając wartość null jako wartość innej operacji zapisu, np. set() lub update(). Możesz użyć tej metody z funkcją update(), aby usunąć wiele elementów podrzędnych za pomocą jednego wywołania interfejsu API.
Otrzymywanie Promise
Aby wiedzieć, kiedy dane zostaną zatwierdzone na serwerze Firebase Realtime Database, możesz użyć
Promise.
Zarówno funkcja set(), jak i update() mogą zwracać Promise, którego możesz użyć, aby wiedzieć, 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ąć pojedynczy detektor, przekazując go jako parametr do funkcji off().
Wywołanie funkcji off() w lokalizacji bez argumentów powoduje usunięcie wszystkich detektorów w tej lokalizacji.
Wywołanie funkcji off() w detektorze nadrzędnym nie powoduje automatycznego usunięcia detektorów zarejestrowanych w jego węzłach podrzędnych. Aby usunąć wywołanie zwrotne, należy też wywołać funkcję off() w przypadku wszystkich detektorów podrzędnych.
Zapisywanie danych jako transakcji
Podczas pracy z danymi, które mogą zostać uszkodzone przez jednoczesne modyfikacje, np. liczniki przyrostowe, możesz użyć operacji transakcji. Możesz przekazać do tej operacji funkcję aktualizacji i opcjonalne wywołanie zwrotne zakończenia. Funkcja aktualizacji przyjmuje jako argument bieżący stan danych i zwraca nowy żądany stan, który chcesz zapisać. Jeśli inny klient zapisze dane w 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 aplikacji do blogowania społecznościowego możesz zezwolić użytkownikom na oznaczanie postów gwiazdką i usuwanie oznaczenia gwiazdką oraz śledzenie liczby 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łowej liczbie gwiazdek, jeśli wielu użytkowników oznaczy ten sam post gwiazdką w tym samym czasie lub jeśli klient miał nieaktualne dane. Jeśli transakcja zostanie odrzucona, serwer zwróci klientowi bieżącą wartość, która ponownie uruchomi transakcję ze zaktualizowaną wartością. Powtarza się to, dopóki transakcja nie zostanie zaakceptowana lub nie zostanie przerwana.
Niepodzielne przyrosty po stronie serwera
W powyższym przypadku użycia zapisujemy w bazie danych 2 wartości: identyfikator użytkownika, który oznaczył post gwiazdką lub usunął gwiazdkę, oraz zwiększoną liczbę gwiazdek. Jeśli wiemy już, że użytkownik oznaczył post gwiazdką, możemy użyć operacji niepodzielnego przyrostu 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 używa operacji transakcji, więc nie jest automatycznie ponownie uruchamiany w przypadku konfliktu aktualizacji. Ponieważ jednak operacja przyrostu odbywa się bezpośrednio na serwerze bazy danych, nie ma ryzyka konfliktu.
Jeśli chcesz wykrywać i odrzucać konflikty specyficzne dla aplikacji, np. gdy użytkownik oznaczy post gwiazdką, który już wcześniej oznaczył, musisz napisać niestandardowe reguły bezpieczeństwa dla tego przypadku użycia.
Praca z danymi offline
Jeśli klient utraci połączenie z siecią, Twoja aplikacja będzie nadal działać prawidłowo.
Każdy klient połączony z bazą danych Firebase utrzymuje własną wewnętrzną wersję aktywnych danych. Gdy dane są zapisywane, najpierw są zapisywane w tej lokalnej wersji. Klient Firebase synchronizuje te dane z serwerami zdalnej bazy danych i innymi klientami na zasadzie „najlepszych starań”.
W rezultacie wszystkie zapisy w bazie danych natychmiast wywołują zdarzenia lokalne, zanim jakiekolwiek dane zostaną zapisane na serwerze. Oznacza to, że Twoja aplikacja pozostaje responsywna niezależnie od opóźnienia sieci lub łączności.
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 działaniu offline znajdziesz w artykule Więcej informacji o funkcjach online i offline.