Czytaj i zapisuj dane w sieci

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

Zanim omówimy sposób, w jaki 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: Pakiet emulatorów lokalnych Firebase. 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ą lokalnego 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ń).

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

  1. Dodanie wiersza kodu do konfiguracji testowej aplikacji, aby połączyć się 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 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 zapoznać się z wprowadzeniem do lokalnego pakietu emulatorów .

Uzyskaj odniesienie do bazy danych

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

Web version 9

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web version 8

var database = firebase.database();

Zapisz dane

W tym dokumencie omówiono podstawy pobierania danych oraz porządkowania i filtrowania danych Firebase.

Dane Firebase są pobierane przez dołączenie asynchronicznego detektora do firebase.database.Reference . Odbiornik jest wyzwalany raz dla początkowego stanu 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 z set() w następujący sposób:

Web version 9

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 version 8

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

Użycie set() powoduje nadpisanie danych w określonej lokalizacji, w tym wszelkich węzłach podrzędnych.

Przeczytaj dane

Posłuchaj wydarzeń o wartości

Aby odczytać dane na ścieżce i nasłuchiwać zmian, użyj onValue() do obserwowania zdarzeń. Możesz użyć tego zdarzenia, aby odczytać statyczne migawki zawartości w danej ścieżce, tak jak istniały w czasie zdarzenia. Ta metoda jest wywoływana raz, gdy odbiornik jest podłączony, i ponownie za każdym razem, gdy dane, w tym dzieci, ulegną zmianie. Do wywołania zwrotnego zdarzenia przekazuje się migawkę zawierającą 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 nim val() .

Poniższy przykład ilustruje aplikację do blogowania społecznościowego pobierającą liczbę gwiazdek posta z bazy danych:

Web version 9

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 version 8

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

Odbiornik otrzymuje snapshot , która zawiera dane w określonej lokalizacji w bazie danych w czasie zdarzenia. Dane z snapshot można pobrać za pomocą metody val() .

Przeczytaj dane raz

Przeczytaj dane raz za pomocą get()

Zestaw SDK jest przeznaczony 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żyć opisanych powyżej technik zdarzeń wartości, aby odczytać dane i otrzymywać powiadomienia o aktualizacjach danych z zaplecza. Techniki nasłuchiwania zmniejszają zużycie i rozliczenia oraz są zoptymalizowane, aby zapewnić użytkownikom jak najlepsze wrażenia w trybie online 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 przeszuka lokalną pamięć podręczną i zwróci błąd, jeśli wartość nadal nie zostanie znaleziona.

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.

Web version 9

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 version 8

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

Przeczytaj dane raz z obserwatorem

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:

Web version 9

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 version 8

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 elementów podrzędnych węzła bez nadpisywania innych węzłów podrzędnych, użyj metody update() .

Wywołując 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 utworzyć post i jednocześnie zaktualizować go do kanału ostatniej aktywności i kanału aktywności użytkownika publikującego za pomocą kodu takiego:

Web version 9

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 version 8

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

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. 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ą atomowe: albo wszystkie aktualizacje powiodą się, albo wszystkie aktualizacje nie powiodą się.

Dodaj zakończenie połączenia zwrotnego

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 do bazy danych. Jeśli wywołanie nie powiodło się, do wywołania zwrotnego przekazywany jest obiekt błędu wskazujący przyczynę niepowodzenia.

Web version 9

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 version 8

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

Otrzymaj 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ą zwrócić Promise , której możesz użyć, aby dowiedzieć się, kiedy zapis jest zatwierdzany w bazie danych.

Odłącz słuchaczy

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

Możesz usunąć pojedynczy odbiornik, przekazując go jako parametr do off() . Wywołanie off() w lokalizacji bez argumentów usuwa wszystkie detektory w tej lokalizacji.

Wywołanie off() na odbiorniku nadrzędnym nie usuwa automatycznie odbiorników zarejestrowanych w jego węzłach podrzędnych; off() musi być również wywołana na wszystkich słuchaczach podrzędnych, aby usunąć wywołanie zwrotne.

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ć 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 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:

Web version 9

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 version 8

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

Korzystanie z transakcji zapobiega błędnym zliczaniu gwiazdek, jeśli wielu użytkowników oznacza ten sam post w tym samym czasie lub klient ma nieaktualne dane. W przypadku odrzucenia transakcji serwer zwraca bieżącą 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 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.

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, jeśli występuje aktualizacja powodująca konflikt. 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 zabezpieczeń 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 zachowuje własną wewnętrzną wersję wszystkich aktywnych danych. Kiedy dane są zapisywane, najpierw są 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ą lokalne zdarzenia, 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 łączności aplikacja otrzymuje odpowiedni zestaw zdarzeń, dzięki czemu klient synchronizuje się z bieżącym stanem serwera bez konieczności pisania kodu niestandardowego.

Więcej o zachowaniu w trybie offline omówimy w artykule Dowiedz się więcej o możliwościach w trybie online i offline ..

Następne kroki