(Opcjonalnie) Prototypowanie i testowanie za pomocą Firebase Emulator Suite
Zanim omówimy, jak Twoja aplikacja odczytuje i zapisuje w Bazie danych czasu rzeczywistego, przedstawmy zestaw narzędzi, których możesz użyć do prototypowania i testowania funkcjonalności Bazy danych czasu rzeczywistego: Firebase Emulator Suite. Jeśli wypróbowujesz różne modele danych, optymalizujesz reguły zabezpieczeń lub pracujesz nad znalezieniem najbardziej opłacalnego sposobu interakcji z zapleczem, możliwość pracy lokalnej bez wdrażania usług na żywo może być świetnym pomysłem.
Emulator bazy danych czasu rzeczywistego jest częścią pakietu emulatorów, który umożliwia aplikacji interakcję z zawartością i konfiguracją emulowanej bazy danych, a także opcjonalnie z emulowanymi zasobami projektu (funkcje, inne bazy danych i reguły zabezpieczeń).emulator_suite_short
Korzystanie z emulatora bazy danych czasu rzeczywistego obejmuje tylko kilka kroków:
- Dodanie wiersza kodu do konfiguracji testowej aplikacji, aby połączyć się z emulatorem.
- Z katalogu głównego lokalnego katalogu projektu uruchom
firebase emulators:start
. - Wykonywanie wywołań z kodu prototypu aplikacji przy użyciu zestawu SDK platformy Realtime Database jak zwykle lub przy użyciu interfejsu API REST Realtime Database.
Dostępny jest szczegółowy przewodnik dotyczący Bazy danych czasu rzeczywistego i funkcji w chmurze . Powinieneś także rzucić okiem na wprowadzenie Emulator Suite .
Uzyskaj referencję bazy danych
Aby odczytać lub zapisać dane z bazy danych, potrzebujesz instancji DatabaseReference
:
DatabaseReference ref = FirebaseDatabase.instance.ref();
Zapisz dane
W tym dokumencie omówiono podstawy odczytywania i zapisywania danych Firebase.
Dane Firebase są zapisywane w DatabaseReference
i pobierane przez oczekiwanie lub nasłuchiwanie zdarzeń emitowanych przez odwołanie. Zdarzenia są emitowane raz dla początkowego stanu danych i ponownie przy każdej zmianie danych.
Podstawowe operacje zapisu
W przypadku podstawowych operacji zapisu można użyć set()
, aby zapisać dane w określonym odwołaniu, zastępując wszelkie istniejące dane w tej ścieżce. Możesz ustawić odwołanie do następujących typów: String
, boolean
, int
, double
, Map
, List
.
Na przykład możesz dodać użytkownika za pomocą set()
w następujący sposób:
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
await ref.set({
"name": "John",
"age": 18,
"address": {
"line1": "100 Mountain View"
}
});
Użycie set()
w ten sposób nadpisuje dane w określonej lokalizacji, w tym wszelkie węzły podrzędne. Jednak nadal możesz zaktualizować dziecko bez przepisywania całego obiektu. Jeśli chcesz zezwolić użytkownikom na aktualizowanie ich profili, możesz zaktualizować nazwę użytkownika w następujący sposób:
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
// Only update the name, leave the age and address!
await ref.update({
"age": 19,
});
Metoda update()
akceptuje podścieżkę do węzłów, umożliwiając jednoczesne aktualizowanie wielu węzłów w bazie danych:
DatabaseReference ref = FirebaseDatabase.instance.ref("users");
await ref.update({
"123/age": 19,
"123/address/line1": "1 Mountain View",
});
Przeczytaj dane
Odczytaj dane, nasłuchując zdarzeń wartości
Aby odczytać dane w ścieżce i nasłuchiwać zmian, użyj właściwości onValue
DatabaseReference
, aby nasłuchiwać DatabaseEvent
s.
Możesz użyć DatabaseEvent
do odczytania danych pod daną ścieżką, ponieważ istnieje w momencie zdarzenia. To zdarzenie jest wywoływane raz, gdy odbiornik jest dołączony, i ponownie za każdym razem, gdy dane, w tym wszelkie elementy podrzędne, ulegną zmianie. Zdarzenie ma właściwość snapshot
zawierającą wszystkie dane w tej lokalizacji, w tym dane podrzędne. Jeśli nie ma danych, właściwość exists
migawki będzie miała wartość false
, a jej właściwość value
będzie miała wartość null.
Poniższy przykład ilustruje aplikację do blogowania społecznościowego pobierającą szczegóły wpisu z bazy danych:
DatabaseReference starCountRef =
FirebaseDatabase.instance.ref('posts/$postId/starCount');
starCountRef.onValue.listen((DatabaseEvent event) {
final data = event.snapshot.value;
updateStarCount(data);
});
Odbiornik odbiera DataSnapshot
, który zawiera dane w określonej lokalizacji w bazie danych w czasie zdarzenia we właściwości value
.
Przeczytaj dane raz
Przeczytaj raz za pomocą get()
Zestaw SDK jest przeznaczony do zarządzania interakcjami z serwerami baz danych, niezależnie od tego, czy Twoja aplikacja jest online, czy offline.
Ogólnie rzecz biorąc, należy użyć opisanych powyżej technik zdarzeń wartości, aby odczytać dane i otrzymywać powiadomienia o aktualizacjach danych z zaplecza. Techniki te zmniejszają zużycie i opłaty oraz są zoptymalizowane, aby zapewnić użytkownikom jak najlepsze wrażenia podczas korzystania z Internetu i offline.
Jeśli potrzebujesz danych tylko raz, możesz użyć get()
, aby uzyskać migawkę danych z bazy danych. Jeśli z jakiegoś powodu get()
nie może zwrócić wartości serwera, klient sprawdzi lokalną pamięć podręczną i zwróci błąd, jeśli wartość nadal nie zostanie znaleziona.
Poniższy przykład ilustruje jednorazowe pobieranie z bazy danych nazwy użytkownika dostępnej publicznie:
final ref = FirebaseDatabase.instance.ref();
final snapshot = await ref.child('users/$userId').get();
if (snapshot.exists) {
print(snapshot.value);
} else {
print('No data available.');
}
Niepotrzebne użycie get()
może zwiększyć wykorzystanie przepustowości i doprowadzić do utraty wydajności, której można zapobiec, korzystając z nasłuchiwania w czasie rzeczywistym, jak pokazano powyżej.
Odczytaj dane raz za pomocą raz()
W niektórych przypadkach możesz chcieć, aby wartość z lokalnej pamięci podręcznej została zwrócona natychmiast, zamiast sprawdzania zaktualizowanej wartości na serwerze. W takich przypadkach można użyć funkcji once()
, aby natychmiast pobrać dane z pamięci podręcznej dysku lokalnego.
Jest to przydatne w przypadku danych, które trzeba załadować tylko raz i nie oczekuje się, że będą się często zmieniać lub wymagają aktywnego nasłuchiwania. Na przykład aplikacja do blogowania w poprzednich przykładach używa tej metody do ładowania profilu użytkownika, gdy rozpoczyna on tworzenie nowego posta:
final event = await ref.once(DatabaseEventType.value);
final username = event.snapshot.value?.username ?? 'Anonymous';
Aktualizacja lub usuwanie danych
Zaktualizuj określone pola
Aby jednocześnie pisać do określonych elementów podrzędnych węzła bez nadpisywania innych węzłów podrzędnych, użyj metody update()
.
Wywołując funkcję update()
, możesz zaktualizować wartości podrzędne niższego poziomu, określając ścieżkę klucza. Jeśli dane są przechowywane w wielu lokalizacjach w celu lepszego skalowania, możesz zaktualizować wszystkie wystąpienia tych danych za pomocą funkcji rozdzielania danych . Na przykład aplikacja do blogowania społecznościowego może chcieć utworzyć post i jednocześnie zaktualizować go do kanału ostatniej aktywności i kanału aktywności użytkownika publikującego. Aby to zrobić, aplikacja do blogowania używa takiego kodu:
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);
}
Ten przykład używa funkcji push()
do utworzenia wpisu w węźle zawierającym wpisy dla wszystkich użytkowników w /posts/$postid
i jednoczesnego pobrania klucza z key
. Klucz może być następnie użyty do utworzenia drugiego wpisu we wpisach użytkownika w /user-posts/$userid/$postid
.
Korzystając z tych ścieżek, możesz wykonywać jednoczesne aktualizacje w wielu lokalizacjach w drzewie JSON za pomocą jednego wywołania update()
, tak jak ten przykład tworzy nowy wpis w obu lokalizacjach. Aktualizacje symultaniczne wykonane w ten sposób są niepodzielne: albo wszystkie aktualizacje się powiodą, albo wszystkie aktualizacje kończą się niepowodzeniem.
Dodaj wywołanie zwrotne dotyczące zakończenia
Jeśli chcesz wiedzieć, kiedy Twoje dane zostały zatwierdzone, możesz zarejestrować wywołania zwrotne zakończenia. Zarówno set()
, jak i update()
zwracają Future
s, do którego można dołączyć wywołania zwrotne sukcesu i błędów, które są wywoływane, gdy zapis został zapisany w bazie danych i gdy wywołanie nie powiodło się.
FirebaseDatabase.instance
.ref('users/$userId/email')
.set(emailAddress)
.then((_) {
// Data saved successfully!
})
.catchError((error) {
// The write failed...
});
Usunąć dane
Najprostszym sposobem usunięcia danych jest wywołanie remove()
w odniesieniu do lokalizacji tych danych.
Możesz również usunąć, określając null jako wartość dla innej operacji zapisu, takiej jak set()
lub update()
. Możesz użyć tej techniki z update()
, aby usunąć wiele elementów podrzędnych w jednym wywołaniu API.
Zapisz dane jako transakcje
Podczas pracy z danymi, które mogą zostać uszkodzone przez współbieżne modyfikacje, takie jak liczniki przyrostowe, można użyć transakcji, przekazując procedurę obsługi transakcji do runTransaction()
. Program obsługi transakcji przyjmuje bieżący stan danych jako argument i zwraca nowy pożądany stan, który chcesz zapisać. Jeśli inny klient zapisuje w lokalizacji przed pomyślnym zapisaniem nowej wartości, funkcja aktualizacji jest wywoływana ponownie z nową bieżącą wartością, a zapis jest ponawiany.
Na przykład w przykładowej aplikacji do blogowania społecznościowego możesz zezwolić użytkownikom na oznaczanie i odznaczanie postów oraz śledzenie liczby gwiazdek, które otrzymał post w następujący sposób:
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);
});
}
Domyślnie zdarzenia są wywoływane przy każdym uruchomieniu funkcji aktualizacji transakcji, więc uruchamiając tę funkcję wiele razy, możesz zobaczyć stany pośrednie. Możesz ustawić applyLocally
na false
, aby pominąć te stany pośrednie i zamiast tego czekać na zakończenie transakcji, zanim zostaną zgłoszone zdarzenia:
await ref.runTransaction((Object? post) {
// ...
}, applyLocally: false);
Wynikiem transakcji jest TransactionResult
, który zawiera informacje, takie jak czy transakcja została zatwierdzona, oraz nowy zrzut:
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
Anulowanie transakcji
Jeśli chcesz bezpiecznie anulować transakcję, wywołaj Transaction.abort()
, aby AbortTransactionException
:
TransactionResult result = await ref.runTransaction((Object? user) {
if (user !== null) {
return Transaction.abort();
}
// ...
});
print(result.committed); // false
Atomowe przyrosty po stronie serwera
W powyższym przypadku zapisujemy do bazy danych dwie wartości: identyfikator użytkownika, który oznacza post gwiazdką/usuwa gwiazdkę, oraz zwiększoną liczbę gwiazdek. Jeśli już wiemy, że użytkownik jest gwiazdą posta, zamiast transakcji możemy użyć atomowej operacji inkrementacji.
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);
}
Ten kod nie używa operacji transakcji, więc nie jest automatycznie uruchamiany ponownie w przypadku konfliktu aktualizacji. Jednak ponieważ operacja inkrementacji odbywa się bezpośrednio na serwerze bazy danych, nie ma szans na konflikt.
Jeśli chcesz wykrywać i odrzucać konflikty specyficzne dla aplikacji, takie jak użytkownik oznaczony gwiazdką posta, który już wcześniej oznaczył, powinieneś napisać niestandardowe reguły bezpieczeństwa dla tego przypadku użycia.
Pracuj z danymi w trybie offline
Jeśli klient utraci połączenie sieciowe, Twoja aplikacja będzie nadal działać poprawnie.
Każdy klient połączony z bazą danych Firebase utrzymuje własną wewnętrzną wersję wszystkich aktywnych danych. Kiedy dane są zapisywane, są najpierw zapisywane w tej lokalnej wersji. Klient Firebase synchronizuje następnie te dane ze zdalnymi serwerami baz danych oraz z innymi klientami na zasadzie „najlepszych starań”.
W rezultacie wszystkie zapisy w bazie danych natychmiast wyzwalają zdarzenia lokalne, zanim jakiekolwiek dane zostaną zapisane na serwerze. Oznacza to, że Twoja aplikacja pozostaje responsywna bez względu na opóźnienie sieci lub łączność.
Po ponownym nawiązaniu łączności aplikacja otrzymuje odpowiedni zestaw zdarzeń, dzięki czemu klient synchronizuje się z bieżącym stanem serwera bez konieczności pisania niestandardowego kodu.
Więcej o zachowaniu w trybie offline omówimy w artykule Dowiedz się więcej o możliwościach trybu online i offline .