Odczytuj i zapisuj dane w Internecie

(Opcjonalnie) Stwórz prototyp i przetestuj za pomocą pakietu Firebase Local Emulator Suite

Zanim porozmawiamy o tym, jak aplikacja odczytuje i zapisuje w bazie danych Realtime Database, przedstawmy zestaw narzędzi, których można użyć do prototypowania i testowania funkcjonalności bazy danych Realtime: Firebase Local Emulator Suite. Jeśli wypróbowujesz różne modele danych, optymalizujesz reguły bezpieczeństwa lub szukasz 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 w czasie rzeczywistym jest częścią pakietu Local Emulator Suite, który umożliwia Twojej aplikacji interakcję z zawartością i konfiguracją emulowanej bazy danych, a także opcjonalnie z emulowanymi zasobami projektu (funkcjami, innymi bazami danych i regułami bezpieczeństwa).

Korzystanie z emulatora bazy danych czasu rzeczywistego obejmuje tylko kilka kroków:

  1. Dodanie wiersza kodu do konfiguracji testowej aplikacji w celu nawiązania połączenia z emulatorem.
  2. Z katalogu głównego lokalnego katalogu projektu uruchom firebase emulators:start .
  3. Wykonywanie wywołań z kodu prototypu aplikacji przy użyciu standardowego pakietu SDK platformy Realtime Database lub interfejsu API REST bazy danych Realtime.

Dostępny jest szczegółowy przewodnik dotyczący bazy danych czasu rzeczywistego i funkcji chmury . Powinieneś także zapoznać się ze wstępem do pakietu Local Emulator Suite .

Uzyskaj odniesienie do bazy danych

Aby odczytać lub zapisać dane z bazy danych, potrzebujesz instancji firebase.database.Reference :

Web modular API

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web namespaced API

var database = firebase.database();

Zapisz dane

W tym dokumencie opisano podstawy pobierania danych oraz porządkowania i filtrowania danych Firebase.

Dane Firebase są pobierane poprzez dołączenie asynchronicznego odbiornika do firebase.database.Reference . Odbiornik jest uruchamiany raz dla stanu początkowego danych i ponownie za każdym razem, gdy dane się zmieniają.

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. Na przykład aplikacja do blogowania społecznościowego może dodać użytkownika za pomocą metody set() w następujący 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
  });
}

Web namespaced API

function writeUserData(userId, name, email, imageUrl) {
  firebase.database().ref('users/' + userId).set({
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

Użycie set() nadpisuje dane w określonej lokalizacji, łącznie ze wszystkimi węzłami podrzędnymi.

Przeczytaj dane

Słuchaj wydarzeń wartościowych

Aby odczytać dane na ścieżce i nasłuchiwać zmian, użyj onValue() do obserwacji zdarzeń. Możesz użyć tego zdarzenia do odczytania statycznych migawek zawartości w danej ścieżce, tak jak istniała w momencie zdarzenia. Ta metoda jest uruchamiana raz, gdy słuchacz jest podłączony, i ponownie za każdym razem, gdy zmieniają się dane, w tym dzieci. Do wywołania zwrotnego zdarzenia przekazywana jest migawka zawierająca wszystkie dane w tej lokalizacji, w tym dane podrzędne. Jeśli nie ma danych, migawka zwróci false , gdy wywołasz exists() i null , gdy wywołasz na niej val() .

Poniższy przykład ilustruje aplikację do blogowania społecznościowego pobierającą liczbę gwiazdek postu 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);
});

Web namespaced API

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

Odbiornik otrzymuje snapshot zawierającą dane w określonej lokalizacji w bazie danych w momencie wystąpienia zdarzenia. Dane ze snapshot można pobrać za pomocą metody val() .

Przeczytaj dane raz

Przeczytaj dane raz za pomocą get()

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

Ogólnie rzecz biorąc, należy używać opisanych powyżej technik zdarzeń wartości do odczytywania danych i otrzymywania powiadomień o aktualizacjach danych z zaplecza. Techniki nasłuchiwania zmniejszają wykorzystanie i rozliczenia oraz są zoptymalizowane, aby zapewnić użytkownikom najlepsze doświadczenia w trybie online i offline.

Jeśli potrzebujesz danych tylko raz, możesz użyć get() w celu uzyskania migawki danych z bazy danych. Jeśli z jakiegoś powodu funkcja 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.

Niepotrzebne użycie funkcji get() może zwiększyć wykorzystanie przepustowości i prowadzić do utraty wydajności, czemu można zapobiec, korzystając z odbiornika działającego 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);
});

Web namespaced API

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

Odczytaj dane raz z obserwatorem

W niektórych przypadkach możesz chcieć natychmiastowego zwrócenia wartości z lokalnej pamięci podręcznej, zamiast sprawdzać, czy na serwerze jest zaktualizowana wartość. W takich przypadkach możesz użyć once() , aby natychmiast pobrać dane z pamięci podręcznej dysku lokalnego.

Jest to przydatne w przypadku danych, które należy załadować tylko raz i nie oczekuje się, że będą się często zmieniać ani wymagać aktywnego nasłuchiwania. Na przykład aplikacja do blogowania z poprzednich przykładów używa tej metody do ładowania profilu użytkownika, gdy rozpoczyna on tworzenie nowego posta:

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

Web namespaced API

var userId = firebase.auth().currentUser.uid;
return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => {
  var username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
});

Aktualizacja lub usuwanie danych

Zaktualizuj określone pola

Aby jednocześnie pisać do określonych węzłó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ę do klucza. Jeśli dane są przechowywane w wielu lokalizacjach w celu lepszego skalowania, możesz zaktualizować wszystkie wystąpienia tych danych, korzystając z rozdzielania danych .

Na przykład aplikacja do blogowania społecznościowego może utworzyć post i jednocześnie zaktualizować go do kanału ostatniej aktywności i kanału aktywności użytkownika zamieszczającego post, 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);
}

Web namespaced API

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 zastosowano metodę push() do utworzenia wpisu w węźle zawierającego posty dla wszystkich użytkowników w /posts/$postid i jednoczesnego pobrania klucza. Klucza można następnie użyć do utworzenia drugiego wpisu w postach użytkownika pod adresem /user-posts/$userid/$postid .

Korzystając z tych ścieżek, możesz wykonywać jednoczesne aktualizacje wielu lokalizacji w drzewie JSON za pomocą jednego wywołania update() , tak jak w tym przykładzie tworzony jest nowy post w obu lokalizacjach. Jednoczesne aktualizacje wykonane w ten sposób mają charakter niepodzielny: albo wszystkie aktualizacje się powiodą, albo wszystkie aktualizacje nie powiodą się.

Dodaj wywołanie zwrotne zakończenia

Jeśli chcesz wiedzieć, kiedy Twoje dane zostały zatwierdzone, możesz dodać wywołanie zwrotne zakończenia. Zarówno 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 nie powiedzie się, do wywołania zwrotnego zostanie przekazany obiekt błędu wskazujący przyczynę niepowodzenia.

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

Web namespaced API

firebase.database().ref('users/' + userId).set({
  username: name,
  email: email,
  profile_picture : imageUrl
}, (error) => {
  if (error) {
    // The write failed...
  } else {
    // Data saved successfully!
  }
});

Usunąć dane

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

Możesz także usunąć, podając null jako wartość innej operacji zapisu, takiej jak set() lub update() . Możesz użyć tej techniki z update() , aby usunąć wiele dzieci w jednym wywołaniu API.

Otrzymaj Promise

Aby wiedzieć, kiedy Twoje dane są przesyłane na serwer bazy danych Firebase Realtime Database, możesz użyć Promise . Zarówno set() jak i update() mogą zwrócić Promise , dzięki której możesz dowiedzieć się, kiedy zapis zostanie zatwierdzony w bazie danych.

Odłącz słuchaczy

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

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

Wywołanie funkcji off() na odbiorniku nadrzędnym nie powoduje automatycznego usunięcia odbiorników zarejestrowanych w jego węzłach podrzędnych; off() należy również wywołać na wszystkich odbiornikach podrzędnych, aby usunąć wywołanie zwrotne.

Zapisz dane jako transakcje

Podczas pracy z danymi, które mogą zostać uszkodzone przez jednoczesne modyfikacje, takie jak liczniki przyrostowe, można użyć operacji transakcyjnej . Możesz nadać tej operacji funkcję aktualizacji i opcjonalne wywołanie zwrotne zakończenia. Funkcja aktualizacji przyjmuje bieżący stan danych jako argument i zwraca nowy żądany stan, który chcesz zapisać. Jeśli inny klient zapisuje do lokalizacji przed pomyślnym zapisaniem nowej wartości, funkcja aktualizacji zostanie wywołana ponownie z nową bieżącą wartością i ponowienie próby zapisu.

Na przykład w przykładowej aplikacji do blogowania społecznościowego możesz zezwolić użytkownikom na oznaczanie postów gwiazdkami i usuwanie ich gwiazdką oraz śledzenie liczby gwiazdek, które otrzymał post, w następujący sposób:

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

Web namespaced API

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łowemu zliczeniu gwiazdek, jeśli wielu użytkowników oznacza ten sam post w tym samym czasie lub klient ma nieaktualne dane. Jeżeli transakcja zostanie odrzucona, serwer zwraca aktualną wartość klientowi, który ponownie uruchamia transakcję ze zaktualizowaną wartością. Powtarza się to do momentu zaakceptowania transakcji lub przerwania transakcji.

Atomowe przyrosty po stronie serwera

W powyższym przypadku zapisujemy do bazy danych dwie wartości: identyfikator użytkownika, który gwiazdką/odznaczył posta gwiazdką oraz zwiększoną liczbę gwiazdek. Jeśli już wiemy, że użytkownik oznacza post gwiazdką, zamiast transakcji możemy zastosować operację przyrostu atomowego.

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

Web namespaced API

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 wykorzystuje operacji transakcyjnej, więc nie jest automatycznie uruchamiany ponownie, jeśli wystąpi konflikt aktualizacji. Ponieważ jednak operacja inkrementacji odbywa się bezpośrednio na serwerze bazy danych, nie ma ryzyka wystąpienia konfliktu.

Jeśli chcesz wykrywać i odrzucać konflikty specyficzne dla aplikacji, np. oznaczenie użytkownika gwiazdką postu, który już wcześniej dodał, 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 podłączony do bazy danych Firebase utrzymuje własną wewnętrzną wersję wszelkich aktywnych danych. Kiedy dane są zapisywane, są najpierw zapisywane w tej wersji lokalnej. Następnie klient Firebase synchronizuje te dane ze zdalnymi serwerami baz danych i innymi klientami, dokładając wszelkich starań.

W rezultacie wszystkie zapisy do bazy danych natychmiast wyzwalają 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 odbiera odpowiedni zestaw zdarzeń, dzięki czemu klient synchronizuje się z bieżącym stanem serwera bez konieczności pisania żadnego niestandardowego kodu.

Porozmawiamy więcej o zachowaniu w trybie offline w Dowiedz się więcej o możliwościach online i offline ..

Następne kroki