Odczyt i zapis danych

(Opcjonalnie) Prototypowanie i testowanie w Pakiecie emulatorów Firebase

Zanim opowiemy o tym, jak Twoja aplikacja odczytuje dane w bazie danych czasu rzeczywistego i zapisy w niej, przedstawimy zestaw narzędzi, które można wykorzystać do prototypowania i testowania Bazy danych czasu rzeczywistego pakietu emulatorów Firebase. Jeśli testujesz inne dane optymalizowanie reguł zabezpieczeń lub wyszukiwanie opłacalny sposób interakcji z backendem, który daje możliwość pracy lokalnej bez wdrażania aktywnych usług może być świetnym pomysłem.

Emulator Bazy danych czasu rzeczywistego jest częścią Pakietu emulatorów, który umożliwia aplikacji interakcję z treścią i konfiguracją emulowanej bazy danych a także opcjonalnie emulowane zasoby projektu (funkcje, inne bazy danych, i reguły zabezpieczeń).emulator_suite_short

Aby użyć emulatora Bazy danych czasu rzeczywistego, wystarczy kilka kroków:

  1. Dodajesz wiersz kodu do konfiguracji testowej aplikacji, aby połączyć się z emulatorem.
  2. Uruchomienie firebase emulators:start w katalogu głównym projektu lokalnego.
  3. Wykonywanie wywołań z prototypowego kodu aplikacji za pomocą platformy Bazy danych czasu rzeczywistego pakietu SDK lub interfejsu API REST Realtime Database.

Dostępny jest szczegółowy instrukcja korzystania z Bazy danych czasu rzeczywistego i Cloud Functions. Zapoznaj się też z wprowadzeniem do Pakietu emulatorów.

Pobieranie odniesienia do bazy danych

Aby móc odczytywać lub zapisywać dane z bazy danych, musisz mieć instancję DatabaseReference:

DatabaseReference ref = FirebaseDatabase.instance.ref();

Zapisywanie danych

Ten dokument zawiera podstawowe informacje o odczytywaniu i zapisywaniu danych Firebase.

Dane Firebase są zapisywane w DatabaseReference i pobierane przez Oczekiwanie lub nasłuchiwanie zdarzeń wysyłanych przez odwołanie. Zdarzenia są emitowane raz dla początkowego stanu danych i ponownie po każdej zmianie danych.

Podstawowe operacje zapisu

W przypadku podstawowych operacji zapisu możesz użyć funkcji set(), aby zapisać dane w określonym zastępując wszystkie istniejące dane w tej ścieżce. Możesz utworzyć odwołanie, na te typy: String, boolean, int, double, Map, List.

Użytkownik z adresem set() możesz np. dodać w ten sposób:

DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");

await ref.set({
  "name": "John",
  "age": 18,
  "address": {
    "line1": "100 Mountain View"
  }
});

Użycie tego parametru set() spowoduje zastąpienie danych w określonej lokalizacji, łącznie z węzłami podrzędnymi. Nadal możesz jednak zaktualizować dziecko bez cały obiekt jest tworzony na nowo. Jeśli chcesz zezwolić użytkownikom na aktualizowanie swoich profili możesz zmienić 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 ścieżkę podrzędną do węzłów, co pozwala aktualizować wiele węzłów w bazie danych:

DatabaseReference ref = FirebaseDatabase.instance.ref("users");

await ref.update({
  "123/age": 19,
  "123/address/line1": "1 Mountain View",
});

Odczyt danych

Odczytuj dane przez nasłuchiwanie zdarzeń wartości

Aby odczytać dane na ścieżce i nasłuchiwać zmian, użyj funkcji Właściwość onValue elementu DatabaseReference do nasłuchiwania DatabaseEvent s

Za pomocą DatabaseEvent możesz odczytywać dane na określonej ścieżce, aktualny w momencie wystąpienia zdarzenia. To zdarzenie jest wywoływane raz, gdy parametr za każdym razem, gdy dane, w tym dzieci, zmian. Zdarzenie ma właściwość snapshot zawierającą wszystkie dane w tym lokalizacji, w tym danych podrzędnych. Jeśli nie ma danych, zrzut Właściwość exists będzie miała wartość false, a jej właściwość value będzie null.

W poniższym przykładzie pokazano, jak aplikacja do blogowania społecznościowego pobiera szczegóły posta z bazy danych:

DatabaseReference starCountRef =
        FirebaseDatabase.instance.ref('posts/$postId/starCount');
starCountRef.onValue.listen((DatabaseEvent event) {
    final data = event.snapshot.value;
    updateStarCount(data);
});

Detektor odbiera obiekt DataSnapshot zawierający dane w określonym miejscu znajduje się w bazie danych w chwili wystąpienia zdarzenia we właściwości value.

Odczytaj dane raz

Odczytaj raz za pomocą get()

Pakiet SDK służy do zarządzania interakcjami z serwerami baz danych niezależnie od tego, czy jest online lub offline.

Ogólnie do odczytu wartości zdarzeń należy używać opisanych powyżej technik aby otrzymywać powiadomienia o aktualizacjach danych z backendu. Te techniki zmniejszają wykorzystanie zasobów i płatności, są zoptymalizowane, aby zapewniać użytkownikom korzystają z internetu i offline.

Jeśli dane są potrzebne tylko raz, możesz użyć funkcji get(), aby uzyskać zrzut z bazy danych. Jeśli z jakiegoś powodu get() nie może zwrócić wartość serwera, klient sprawdzi pamięć podręczną w lokalnej pamięci masowej i zwróci błąd , jeśli nadal nie możesz znaleźć wartości.

Ten przykład pokazuje, jak pobrać nazwę użytkownika widoczną publicznie z bazy danych:

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. Można to zapobiec za pomocą detektora w czasie rzeczywistym, powyżej.

Odczytaj dane raz za pomocą funkcji Raz()

W niektórych przypadkach możesz chcieć, aby zwracana była wartość z lokalnej pamięci podręcznej natychmiast, bez sprawdzania aktualizacji na serwerze. W tych przypadków możesz użyć funkcji once(), aby pobrać dane z pamięci podręcznej dysku lokalnego natychmiast.

Jest to przydatne w przypadku danych, które trzeba wczytać tylko raz i w pewnym stopniu często się zmieniają lub wymagają aktywnego słuchania. Na przykład aplikacja do blogowania w poprzednich przykładach korzysta z tej metody do wczytywania profilu użytkownika, gdy rozpocznij tworzenie nowego posta:

final event = await ref.once(DatabaseEventType.value);
final username = event.snapshot.value?.username ?? 'Anonymous';

Aktualizowanie lub usuwanie danych

Zaktualizuj określone pola

Jednoczesne zapisywanie do określonych elementów podrzędnych węzła bez zastępowania innych węzłów podrzędnych, użyj metody update().

Wywołując update(), możesz zaktualizować wartości podrzędne niższego poziomu przez i podaj ścieżkę dostępu do klucza. Jeśli dane są przechowywane w wielu lokalizacjach na potrzeby skalowania możesz zaktualizować wszystkie wystąpienia tych danych za pomocą funkcji rozpowszechnianie danych. Na przykład plik aplikacja do obsługi blogów społecznościowych może utworzyć posta i jednocześnie zaktualizować go kanał ostatniej aktywności i kanał aktywności użytkownika publikującego. W tym celu aplikacja do tworzenia blogów 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);
}

W tym przykładzie użyto metody push() do utworzenia w węźle posta posta zawierającego posty dla: wszystkich użytkowników w organizacji /posts/$postid i jednocześnie pobieraj klucz za pomocą polecenia key Za pomocą klucza można utworzyć drugi wpis w posty na blogu /user-posts/$userid/$postid.

Korzystając z tych ścieżek, można wprowadzać zmiany w wielu lokalizacjach jednocześnie drzewo JSON z pojedynczym wywołaniem update(), jak w tym przykładzie utworzy nowy post w obu lokalizacjach. Równoczesne aktualizacje dokonywane w ten sposób są niepodzielne: wszystkie aktualizacje zakończą się sukcesem, albo wszystkie aktualizacje kończą się niepowodzeniem.

Dodaj pełne wywołanie zwrotne

Jeśli chcesz się dowiedzieć, kiedy Twoje dane zostały zatwierdzone, zarejestruj się wywołania zwrotne do ukończenia. Zarówno set(), jak i update() zwracają wartości Future, do których możesz dołączyć wywołania zwrotne powodzenia i błędu, które są wywoływane, gdy zapis ma dla bazy 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...
    });

Usuń dane

Najprostszym sposobem usunięcia danych jest wywołanie funkcji remove() w odniesieniu do lokalizacji danych.

Możesz też usunąć wartość, określając wartość null w innej operacji zapisu. na przykład set() lub update(). Tej techniki możesz używać w połączeniu z usługą update(), aby: usunąć wiele elementów podrzędnych w jednym wywołaniu interfejsu API.

Zapisywanie danych jako transakcji

Podczas pracy z danymi, które mogą ulec uszkodzeniu na skutek równoczesnych modyfikacji, takich jak liczniki przyrostowe, możesz użyć transakcji, przekazując modułu obsługi transakcji na runTransaction(). Moduł obsługi transakcji pobiera bieżącego stanu danych jako argumentu oraz zwraca nowy pożądany stan, który ma być zapisany. Jeśli inny klient zapisuje w lokalizacji przed zapisaniem nowej wartości, funkcja aktualizacji jest wywoływana ponownie z nową bieżącą wartością, a zapis podjęto próbę ponowienia próby.

Na przykład w przykładowej aplikacji do blogowania społecznościowego można zezwolić użytkownikom i usuwać oznaczenia gwiazdką oraz śledzić liczbę przyznanych przez niego gwiazdek:

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ą zgłaszane przy każdym uruchomieniu funkcji aktualizacji transakcji, więc gdy uruchomisz funkcję wiele razy, możesz zobaczyć stany pośrednie. Możesz ustawić applyLocally na false, aby pomijać te stany pośrednie i zamiast tego poczekaj na zakończenie transakcji przed wygenerowaniem zdarzeń:

await ref.runTransaction((Object? post) {
  // ...
}, applyLocally: false);

Wynikiem transakcji jest plik TransactionResult, który zawiera informacje takie jak to, 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ę, zadzwoń pod numer Transaction.abort(), aby wyślij AbortTransactionException:

TransactionResult result = await ref.runTransaction((Object? user) {
  if (user !== null) {
    return Transaction.abort();
  }

  // ...
});

print(result.committed); // false

Atomic przyrosty po stronie serwera

W powyższym przypadku użycia zapisujemy w bazie danych 2 wartości: identyfikator użytkownik, który oznacza post lub oznaczenie gwiazdką posta, i zwiększona liczba gwiazdek. Jeśli że użytkownik oznaczy post gwiazdką, możemy użyć przyrostu atomowego zamiast transakcji.

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 korzysta z operacji transakcji, więc nie pobiera automatycznie w przypadku wystąpienia konfliktu aktualizacji uruchom ponownie system. Ponieważ jednak operacja przyrostu odbywa się bezpośrednio na serwerze bazy danych, nie występuje konflikt.

Jeśli chcesz wykrywać i odrzucać konflikty dotyczące aplikacji, na przykład w postach, które zostały już wcześniej oznaczone gwiazdką, należy napisać niestandardowy dla konkretnego przypadku użycia.

Praca z danymi w trybie offline

Jeśli klient utraci połączenie sieciowe, aplikacja będzie nadal działać .

Każdy klient połączony z bazą danych Firebase ma własną wersję wewnętrzną wszystkich aktywnych danych. Podczas zapisywania danych są one zapisywane w tej wersji lokalnej . Klient Firebase następnie synchronizuje te dane ze zdalną bazą danych. z serwerami Google i innymi klientami w ramach „najlepszych starań”, podstaw.

W rezultacie wszystkie zapisy w bazie danych wywołują zdarzenia lokalne natychmiast, przed jakiekolwiek dane są zapisywane na serwerze. Oznacza to, że aplikacja pozostaje niezależnie od opóźnienia sieciowego czy połączenia.

Po przywróceniu połączenia aplikacja otrzyma odpowiedni zestaw zdarzeń, aby klient synchronizował się z bieżącym stanem serwera bez konieczności napisać dowolny niestandardowy kod.

Więcej informacji o zachowaniu użytkowników offline Więcej informacji o możliwościach pracy online i offline

Dalsze kroki