Odczyt i zapis danych w internecie

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

Zanim porozmawiamy o tym, jak Twoja aplikacja odczytuje dane w Bazie danych czasu rzeczywistego i zapisuje je w tej bazie, omówię zestaw narzędzi, których możesz użyć do prototypowania i testowania funkcji Bazy danych czasu rzeczywistego: Pakiet emulatorów lokalnych Firebase. Jeśli wypróbowujesz różne modele danych, optymalizujesz reguły zabezpieczeń lub szukasz najbardziej ekonomicznego sposobu interakcji z backendem, 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 lokalnych, który umożliwia aplikacji interakcję z treścią i konfiguracją emulowanej bazy danych, a także opcjonalnie emulowanymi zasobami projektu (funkcjami, innymi bazami danych i regułami zabezpieczeń).

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. Wywoływanie z prototypowego kodu aplikacji za pomocą pakietu SDK platformy Baza danych czasu rzeczywistego lub interfejsu API REST Bazy danych czasu rzeczywistego.

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

Pobieranie odniesienia do bazy danych

Aby móc odczytywać lub zapisywać dane z bazy danych, potrzebujesz instancji firebase.database.Reference:

Web Modular API

import { getDatabase } from "firebase/database";

const database = getDatabase();

Interfejs API internetowej przestrzeni nazw

var database = firebase.database();

Zapisywanie danych

W tym dokumencie znajdziesz podstawowe informacje o pobieraniu danych oraz sposobie ich porządkowania i filtrowania.

Dane Firebase są pobierane przez podłączenie detektora asynchronicznego do firebase.database.Reference. Detektor jest aktywowany raz dla początkowego stanu danych i ponownie przy każdej zmianie danych.

Podstawowe operacje zapisu

W przypadku podstawowych operacji zapisu możesz użyć polecenia set(), aby zapisać dane w określonym odwołaniu, zastępując wszystkie dotychczasowe dane w tej ścieżce. Na przykład aplikacja do blogowania społecznościowego może dodać użytkownika z elementem set() w taki sposób:

Web Modular API

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

Interfejs API internetowej przestrzeni nazw

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.

Odczyt danych

Wykrywaj zdarzenia dotyczące wartości

Aby odczytywać dane na ś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 wystąpienia zdarzenia. Metoda ta jest wyzwalana raz, gdy podłączono detektor, i ponownie za każdym razem, gdy dane, w tym elementy podrzędne, zostaną zmienione. Wywołanie zwrotne zdarzenia jest przekazywane do zrzutu zawierającego wszystkie dane w danej lokalizacji, w tym dane podrzędne. Jeśli nie ma danych, zrzut zwróci wartość false, gdy wywołasz exists() i null, gdy wywołasz na nim val().

Ten przykład przedstawia aplikację do obsługi blogów społecznościowych, która pobiera liczbę gwiazdek z bazy danych:

Web Modular API

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

Interfejs API internetowej przestrzeni nazw

var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

Detektor otrzymuje obiekt snapshot zawierający dane z określonej lokalizacji w bazie danych w momencie wystąpienia zdarzenia. Dane z narzędzia snapshot można pobrać przy użyciu metody val().

Odczytaj dane raz

Jednorazowy 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 działa 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 dotyczących wartości zdarzeń. Te metody ograniczają wykorzystanie zasobów i płatności, a także są zoptymalizowane pod kątem maksymalnej wygody użytkowników korzystających z internetu i offline.

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

Niepotrzebne użycie get() może zwiększyć wykorzystanie przepustowości i spowodować utratę wydajności. Można temu zapobiec, korzystając z detektora w czasie rzeczywistym (jak pokazano powyżej).

Web Modular API

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

Interfejs API internetowej przestrzeni nazw

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 z obserwatorem

W niektórych przypadkach możesz chcieć, aby wartość z lokalnej pamięci podręcznej była zwracana natychmiast, zamiast sprawdzania aktualizacji na serwerze. W takich przypadkach możesz użyć polecenia 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 nie będą się często zmieniać ani wymagać aktywnego słuchania. Na przykład aplikacja do blogowania z poprzednich przykładów używa tej metody do wczytywania profilu użytkownika, gdy zaczyna pisać nowy post:

Web Modular API

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

Interfejs API internetowej przestrzeni nazw

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 lub usuwanie danych

Zaktualizuj określone pola

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().

Gdy wywołujesz update(), możesz zaktualizować wartości podrzędne niższego poziomu, podając ścieżkę klucza. Jeśli dane są przechowywane w kilku lokalizacjach w celu zwiększenia ich skalowania, możesz zaktualizować wszystkie ich wystąpienia za pomocą przekazywania danych na zewnątrz.

Na przykład aplikacja do obsługi blogów społecznościowych może utworzyć posta i jednocześnie aktualizować go do kanału ostatniej aktywności i kanału aktywności użytkownika publikującego, używając takiego kodu:

Web Modular API

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

Interfejs API internetowej przestrzeni nazw

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żyto metody push() do utworzenia w węźle posta zawierającego posty dla wszystkich użytkowników w domenie /posts/$postid oraz jednocześnie pobranie klucza. Klucza można następnie użyć do utworzenia drugiego wpisu w postach użytkownika pod adresem /user-posts/$userid/$postid.

Za pomocą tych ścieżek możesz aktualizować wiele lokalizacji w drzewie JSON za pomocą jednego wywołania update(). Tak jak w tym przykładzie, w obu lokalizacjach. Aktualizacje wprowadzane w ten sposób są niepodzielne: albo wszystkie aktualizacje zakończą się sukcesem, albo wszystkie się kończą.

Dodaj zakończone wywołanie zwrotne

Jeśli chcesz się dowiedzieć, kiedy dane zostały zatwierdzone, możesz dodać wywołanie zwrotne uzupełnienia. Zarówno set(), jak i update() przyjmuje opcjonalne wywołanie zwrotne ukończenia, które jest wywoływane po zatwierdzeniu zapisu w bazie danych. Jeśli wywołanie się nie udało, wywołanie zwrotne jest przekazywane obiekt błędu z informacją o przyczynie tego błędu.

Web Modular API

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

Interfejs API internetowej przestrzeni nazw

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 metody remove() w odniesieniu do lokalizacji tych danych.

Możesz też usunąć plik, określając null jako wartość innej operacji zapisu, np. set() lub update(). Możesz używać tej metody razem z funkcją update(), aby usunąć wiele elementów podrzędnych w jednym wywołaniu interfejsu API.

Odbierz Promise

Aby dowiedzieć się, kiedy Twoje dane są przekazywane do serwera Bazy danych czasu rzeczywistego Firebase, możesz użyć Promise. Zarówno set(), jak i update() mogą zwracać błąd Promise, dzięki któremu można się dowiedzieć, kiedy zapis w bazie danych jest zatwierdzony.

Odłącz detektory

Wywołania zwrotne są usuwane poprzez wywołanie metody off() w odniesieniu do bazy danych Firebase.

Możesz usunąć pojedynczy detektor, przekazując go jako parametr do off(). Wywołanie off() w lokalizacji bez argumentów powoduje usunięcie wszystkich detektorów w tej lokalizacji.

Wywołanie off() w detektorze nadrzędnym nie powoduje automatycznego usunięcia detektorów zarejestrowanych w węzłach podrzędnych. Aby usunąć wywołanie zwrotne, funkcja off() musi też zostać wywołana we wszystkich detektorach podrzędnych.

Zapisywanie danych jako transakcji

Podczas pracy z danymi, które mogą ulec uszkodzeniu w wyniku równoczesnych modyfikacji, takich jak liczniki przyrostowe, możesz użyć operacji transakcji. Możesz do tej operacji dodać funkcję aktualizacji i opcjonalne wywołanie zwrotne zakończenia. Funkcja aktualizacji przyjmuje bieżący stan danych jako argument i zwraca nowy pożądany stan do zapisania. Jeśli inny klient zapisze dane w lokalizacji, zanim Twoja nowa wartość zostanie zapisana, funkcja aktualizacji zostanie wywołana 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 umożliwić użytkownikom oznaczanie postów gwiazdką i usuwanie gwiazdek oraz śledzenie liczby przyznanych gwiazdek:

Web Modular API

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

Interfejs API internetowej przestrzeni nazw

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ło gwiazdką ten sam post w tym samym czasie lub klient miał nieaktualne dane. W przypadku odrzucenia transakcji serwer zwraca bieżącą wartość klientowi, który uruchamia transakcję ponownie ze zaktualizowaną wartością. Powtarza się, dopóki transakcja nie zostanie zaakceptowana lub nie przerwiesz.

Atomic przyrosty po stronie serwera

W powyższym przypadku użycia zapisujemy w bazie danych 2 wartości: identyfikator użytkownika, który postawił gwiazdką lub usunął oznaczenie gwiazdką, oraz zwiększoną liczbę gwiazdek. Jeśli wiemy już, że użytkownik oznacza posta gwiazdką, możemy użyć niepodzielnej operacji przyrostu zamiast transakcji.

Web Modular API

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

Interfejs API internetowej przestrzeni nazw

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 uruchamiany ponownie w przypadku wystąpienia konfliktu aktualizacji. Ponieważ jednak operacja zwiększania odbywa się bezpośrednio na serwerze bazy danych, nie ma ryzyka konfliktu.

Jeśli chcesz wykrywać i odrzucać konflikty dotyczące aplikacji (np. gdy użytkownik oznacza posta gwiazdką już wcześniej), musisz utworzyć niestandardowe reguły zabezpieczeń odpowiednie do tego zastosowania.

Praca z danymi w trybie offline

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

Każdy klient połączony z bazą danych Firebase zachowuje własną, wewnętrzną wersję wszystkich aktywnych danych. Podczas zapisywania danych są one najpierw zapisywane w tej wersji lokalnej. Następnie klient Firebase synchronizuje te dane ze zdalnymi serwerami baz danych i innymi klientami w ramach najlepszych starań.

W rezultacie wszystkie zapisy w bazie danych natychmiast uruchamiają zdarzenia lokalne, zanim jakiekolwiek dane zostaną zapisane na serwerze. Oznacza to, że aplikacja pozostaje elastyczna bez względu na opóźnienia sieciowe czy połączenie.

Gdy połączenie zostanie przywrócone, aplikacja otrzyma odpowiedni zestaw zdarzeń, aby klient mógł synchronizować się z obecnym 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.

Dalsze kroki