Odczyt i zapis danych w internecie

(Opcjonalnie) Prototypowanie i testowanie za pomocą Firebase Local Emulator Suite

Zanim omówimy sposób odczytu i zapisu danych do Realtime Database przez aplikację, zapoznamy Cię z zestawem narzędzi, które możesz wykorzystać do tworzenia prototypów 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 back-endem, warto pracować lokalnie bez wdrażania usług na żywo.

Emulator Realtime Database jest częścią Local Emulator Suite, która 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 zabezpieczeń).

Korzystanie z emulatora Realtime Database wymaga wykonania kilku czynności:

  1. Dodanie linii kodu do konfiguracji testowej aplikacji, aby połączyć się z emulatorem.
  2. W katalogu głównym lokalnego katalogu projektu uruchom firebase emulators:start.
  3. Wykonywanie wywołań z prototypowego kodu aplikacji za pomocą pakietu SDK platformy Realtime Database w zwykły sposób lub za pomocą interfejsu API REST Realtime Database.

Dostępny jest szczegółowy samouczek dotyczący funkcji Realtime DatabaseCloud Functions. Zapoznaj się też z Local Emulator Suitewprowadzeniem.

Pobieranie odniesienia do bazy danych

Aby odczytywać lub zapisywać dane w bazie danych, musisz mieć instancję 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 ich sortowaniu i filtrowaniu.

Dane Firebase są pobierane przez dołączenie asynchronicznego odbiornika do firebase.database.Reference. Listener jest uruchamiany raz w przypadku początkowego stanu danych i ponownie za każdym razem, gdy dane ulegną zmianie.

Podstawowe operacje zapisu

W przypadku podstawowych operacji zapisu możesz użyć funkcji set(), aby zapisać dane w określonej referencji, zastępując wszystkie istniejące dane na tej ścieżce. Na przykład aplikacja do blogowania społecznościowego może dodać użytkownika z użyciem 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 polecenia 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 odczytywać dane na ścieżce i obserwować zmiany, użyj funkcji onValue() do obserwowania zdarzeń. Za pomocą tego zdarzenia możesz odczytać statyczne migawki treści w danej ścieżce w stanie, w jakim były one w momencie zdarzenia. Ta metoda jest wywoływana raz, gdy słuchacz zostanie dołączony, i ponownie za każdym razem, gdy dane, w tym elementy podrzędne, ulegną zmianie. Funkcja wywołania z powrotu zdarzenia otrzymuje migawkę zawierającą wszystkie dane w danej lokalizacji, w tym dane podrzędne. Jeśli nie ma żadnych danych, snapshot zwróci wartość false, gdy wywołasz exists(), i null, gdy wywołasz val().

Ten 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);
});

Listener otrzymuje snapshot, który zawiera dane z określonej lokalizacji w bazie danych w momencie zdarzenia. Dane w snapshot możesz pobrać za pomocą metody val().

odczytywanie danych raz;

jednokrotny odczyt danych za pomocą funkcji get().

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

Ogólnie zalecamy używanie opisanych powyżej metod zdarzeń wartościowych do odczytywania danych, aby otrzymywać powiadomienia o ich aktualizacjach z back-endu. Techniki słuchania zmniejszają wykorzystanie i obciążenia, a także są optymalizowane pod kątem zapewnienia użytkownikom najlepszych wrażeń w sieci i poza nią.

Jeśli dane są potrzebne tylko raz, możesz użyć funkcji get(), aby uzyskać podsumowanie 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żywanie funkcji get() może zwiększyć wykorzystanie przepustowości i spowodować spadek wydajności. Można temu zapobiec, używając odbiorcy 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);
});

jednokrotny odczyt danych z obserwatorem,

W niektórych przypadkach możesz chcieć, aby wartość z lokalnej pamięci podręcznej była zwracana natychmiast zamiast sprawdzać zaktualizowaną wartość na serwerze. W takich przypadkach możesz użyć polecenia 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 słuchania. Na przykład aplikacja do blogowania w poprzednich przykładach używa tej metody do wczytania profilu użytkownika, gdy ten zaczyna 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 konkretnych pól

Aby jednocześnie zapisywać dane w określonych węzłach podrzędnych bez nadpisywania 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ę klucza. Jeśli dane są przechowywane w kilku lokalizacjach, aby zapewnić lepszą skalowalność, możesz zaktualizować wszystkie wystąpienia tych danych za pomocą rozgałęzienia danych.

Na przykład aplikacja do blogowania społecznościowego może tworzyć posty i jednocześnie aktualizować je w strumieniach aktywności z ostatnich wpisów i strumieniach 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 funkcja push() służy do tworzenia postu w węźle zawierającym posty wszystkich użytkowników w węźle /posts/$postid i jednoczesnego pobierania klucza. Następnie można użyć klucza do utworzenia drugiego wpisu w postach użytkownika na stronie /user-posts/$userid/$postid.

Dzięki tym ścieżkom możesz przeprowadzać jednoczesne aktualizacje wielu lokalizacji w drzewie JSON za pomocą jednego wywołania funkcji update(), jak w tym przykładzie, w którym tworzy nowy post w obu lokalizacjach. Jednoczesne aktualizacje w ten sposób są atomowe: albo wszystkie się udają, albo wszystkie się nie udają.

Dodawanie wywołania zwrotnego po zakończeniu

Jeśli chcesz wiedzieć, kiedy dane zostały zapisane, możesz dodać wywołanie zwrotne po zakończeniu. Zarówno set(), jak i update() przyjmują opcjonalną funkcję zwracania wartości, która jest wywoływana po zaakceptowaniu zapisu w bazie danych. Jeśli wywołanie zakończyło się niepowodzeniem, wywołanie zwrotne 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 usuwania danych jest wywołanie funkcji remove() z odniesieniem do lokalizacji tych danych.

Możesz też usunąć wartość, podając null jako wartość dla innej operacji zapisu, takiej jak set() lub update(). Możesz użyć tej metody z update(), aby usunąć wiele elementów podrzędnych w jednym wywołaniu interfejsu API.

Otrzymano „Promise

Aby dowiedzieć się, kiedy Twoje dane zostaną zapisane na serwerze Firebase Realtime Database, możesz użyć Promise. Zarówno set(), jak i update() mogą zwracać wartość Promise, która pozwala określić, kiedy zapis został zaakceptowany w bazie danych.

Odłączanie słuchaczy

Callbacki są usuwane przez wywołanie metody off() w odwołaniu do bazy danych Firebase.

Możesz usunąć pojedynczego słuchacza, przekazując go jako parametr do funkcji off(). Wywołanie funkcji off() w przypadku lokalizacji bez argumentów powoduje usunięcie wszystkich słuchaczy w tej lokalizacji.

Wywołanie metody off() na odbiorcy nadrzędnym nie powoduje automatycznego usuwania odbiorców zarejestrowanych na jego węzłach podrzędnych. Aby usunąć wywołania zwrotne, należy też wywołać metodę off() na wszystkich odbiorcach podrzędnych.

Zapisywanie danych jako transakcji

Podczas pracy z danymi, które mogą zostać uszkodzone przez równoległe modyfikacje, np. z licznikami przyrostowymi, możesz użyć operacji transakcji. Możesz podać dla tej operacji funkcję aktualizacji i opcjonalnie funkcję wywołania zwrotnego po zakończeniu. Funkcja update przyjmuje jako argument bieżący stan danych i zwraca nowy pożądany stan, który chcesz zapisać. Jeśli inny klient zapisze w tej lokalizacji dane, zanim uda się zapisać nową wartość, funkcja aktualizacji zostanie wywołana ponownie z nową bieżącą wartością i ponownie spróbuje zapisać dane.

Na przykład w przypadku aplikacji do prowadzenia bloga społecznościowego możesz zezwolić użytkownikom na oznaczanie postów gwiazdką i usuwanie tej oceny oraz śledzenie, ile gwiazdek otrzymał dany post. W tym celu:

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;
  });
}

Transakcja zapobiega błędnym wartościom liczby gwiazdek, jeśli więcej użytkowników oznaczy gwiazdką ten sam post w tym samym czasie lub jeśli klient ma nieaktualne dane. Jeśli transakcja zostanie odrzucona, serwer zwróci bieżącą wartość klientowi, który ponownie wykona transakcję z aktualną wartością. Czynność ta jest powtarzana do momentu zaakceptowania transakcji lub jej przerwania.

Atomowe zwiększenia po stronie serwera

W tym przypadku do bazy danych zapisujemy 2 wartości: identyfikator użytkownika, który oznaczył post gwiazdką lub odznaczył go z gwiazdką, oraz zwiększoną liczbę gwiazdek. Jeśli wiemy, że użytkownik oznaczył post jako ulubiony, możemy użyć operacji atomowej zwiększania 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 ponownie uruchamiany, jeśli wystąpiła aktualizacja powodująca konflikt. Operacja zwiększania wartości występuje jednak bezpośrednio na serwerze bazy danych, więc nie ma możliwości wystąpienia konfliktu.

Jeśli chcesz wykrywać i odrzucać konflikty związane z konkretną aplikacją, np. gdy użytkownik wyróżnia wpis, który wyróżnił już wcześniej, na potrzeby takiego przypadku użycia napisz niestandardowe reguły bezpieczeństwa.

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ą wersję wewnętrzną wszystkich aktywnych danych. Dane są najpierw zapisywane w tej wersji lokalnej. Następnie klient Firebase synchronizuje te dane ze zdalnymi serwerami bazy danych i z innymi klientami według zasady „najlepszego wysiłku”.

W rezultacie wszystkie zapisy w bazie danych powodują natychmiastowe wywołanie zdarzeń lokalnych, zanim jakiekolwiek dane zostaną zapisane na serwerze. Oznacza to, że aplikacja pozostaje responsywna niezależnie od opóźnień w 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 kodu niestandardowego.

Więcej informacji o działaniu offline znajdziesz w artykule Więcej informacji o możliwościach online i offline.

Dalsze kroki