Leggere e scrivere dati sul web

(Facoltativo) Crea un prototipo e testa con Firebase Local Emulator Suite

Prima di parlare di come la tua app legge e scrive in Realtime Database, introduciamo un insieme di strumenti che puoi utilizzare per realizzare prototipi e testare la funzionalità di Realtime Database: Firebase Local Emulator Suite. Se stai provando diversi modelli di dati, ottimizzando le regole di sicurezza o cercando di trovare il modo più economico per interagire con il back-end, la possibilità di lavorare localmente senza implementare i servizi in produzione può essere un'ottima idea.

Un emulatore Realtime Database fa parte di Local Emulator Suite, che consente all'app di interagire con i contenuti e la configurazione del database emulati, nonché, facoltativamente, con le risorse del progetto emulate (funzioni, altri database e regole di sicurezza).

L'utilizzo dell'emulatore Realtime Database prevede solo pochi passaggi:

  1. Aggiungere una riga di codice alla configurazione di test dell'app per connettersi all'emulatore.
  2. Dalla directory principale del progetto locale, esegui firebase emulators:start.
  3. Effettuare chiamate dal codice del prototipo dell'app utilizzando un SDK della piattaforma Realtime Database come di consueto o l'API REST Realtime Database.

È disponibile una procedura dettagliata che coinvolge Realtime Database e Cloud Functions. Consulta anche l'introduzione a Local Emulator Suite.

Recuperare un riferimento a un database

Per leggere o scrivere dati dal database, devi avere un'istanza di firebase.database.Reference:

Web

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web

var database = firebase.database();

Scrivi dati

Questo documento illustra le nozioni di base sul recupero dei dati e su come ordinare e filtrare i dati di Firebase.

I dati di Firebase vengono recuperati collegando un ascoltatore asincrono a un firebase.database.Reference. L'ascoltatore viene attivato una volta per lo stato iniziale dei dati e di nuovo ogni volta che i dati cambiano.

Operazioni di scrittura di base

Per le operazioni di scrittura di base, puoi utilizzare set() per salvare i dati in un riferimento specificato, sostituendo eventuali dati esistenti in quel percorso. Ad esempio, un'applicazione di social blogging potrebbe aggiungere un utente con set() come segue:

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

L'utilizzo di set() sovrascrive i dati nella posizione specificata, inclusi eventuali nodi secondari.

Lettura di dati

Ascolta gli eventi relativi ai valori

Per leggere i dati in un percorso e rilevare le modifiche, utilizza onValue() per osservare gli eventi. Puoi utilizzare questo evento per leggere istantanee statiche dei contenuti in un determinato percorso, così come esistevano al momento dell'evento. Questo metodo viene attivato una volta quando l'ascoltatore è collegato e di nuovo ogni volta che i dati, inclusi i figli, cambiano. Al callback dell'evento viene passato uno snapshot contenente tutti i dati in quella posizione, inclusi i dati secondari. Se non sono presenti dati, lo snapshot restituirà false quando chiami exists() e null quando chiami val().

L'esempio seguente mostra un'applicazione di blogging sociale che recupera il numero di stelle di un post dal database:

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

L'ascoltatore riceve un snapshot contenente i dati nella posizione specificata nel database al momento dell'evento. Puoi recuperare i dati in snapshot con il metodo val().

Leggi i dati una volta

Leggere i dati una volta con get()

L'SDK è progettato per gestire le interazioni con i server di database indipendentemente dal fatto che la tua app sia online o offline.

In genere, devi utilizzare le tecniche di evento con valore descritte sopra per leggere i dati e ricevere notifiche degli aggiornamenti dei dati dal backend. Le tecniche di ascolto riducono l'utilizzo e la fatturazione e sono ottimizzate per offrire ai tuoi utenti un'esperienza migliore quando sono online e offline.

Se hai bisogno dei dati una sola volta, puoi utilizzare get() per ottenere uno snapshot dei dati dal database. Se per qualsiasi motivo get() non è in grado di restituire il valore del server, il client esaminerà la cache dello spazio di archiviazione locale e restituirà un errore se il valore non viene ancora trovato.

L'uso non necessario di get() può aumentare l'utilizzo della larghezza di banda e comportare una perdita di prestazioni, che può essere evitata utilizzando un ascoltatore in tempo reale come mostrato sopra.

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

Leggere i dati una volta con un osservatore

In alcuni casi potresti voler che il valore della cache locale venga restituito immediatamente, anziché controllare se è presente un valore aggiornato sul server. In questi casi, puoi utilizzare once() per recuperare immediatamente i dati dalla cache locale del disco.

Questo è utile per i dati che devono essere caricati una sola volta e che non dovrebbero cambiare frequentemente o richiedere l'ascolto attivo. Ad esempio, l'app di blogging indicata negli esempi precedenti utilizza questo metodo per caricare il profilo di un utente quando inizia a scrivere un nuovo 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';
  // ...
});

Aggiornamento o eliminazione di dati

Aggiornare campi specifici

Per scrivere contemporaneamente in nodi secondari specifici di un nodo senza sovrascrivere altri nodi secondari, utilizza il metodo update().

Quando chiami update(), puoi aggiornare i valori secondari di livello inferiore specificando un percorso per la chiave. Se i dati vengono archiviati in più posizioni per una migliore scalabilità, puoi aggiornare tutte le istanze utilizzando la distribuzione dei dati.

Ad esempio, un'app di social blogging potrebbe creare un post e aggiornarlo contemporaneamente nel feed delle attività recenti e nel feed delle attività dell'utente che ha pubblicato il post utilizzando un codice come questo:

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

Questo esempio utilizza push() per creare un post nel nodo contenente i post per tutti gli utenti in /posts/$postid e contemporaneamente recuperare la chiave. La chiave può essere quindi utilizzata per creare una seconda voce nei post dell'utente su /user-posts/$userid/$postid.

Utilizzando questi percorsi, puoi eseguire aggiornamenti simultanei in più posizioni nella struttura ad albero JSON con una singola chiamata a update(), ad esempio come in questo esempio che crea il nuovo post in entrambe le posizioni. Gli aggiornamenti simultanei eseguiti in questo modo sono atomici: o tutti gli aggiornamenti vanno a buon fine o tutti non vanno a buon fine.

Aggiungere un callback di completamento

Se vuoi sapere quando i dati sono stati sottoposti a commit, puoi aggiungere un callback di completamento. Sia set() che update() accettano un callback di completamento facoltativo che viene chiamato quando la scrittura è stata eseguita nel database. Se la chiamata non è andata a buon fine, al callback viene passato un oggetto errore che indica il motivo dell'errore.

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

Elimina dati

Il modo più semplice per eliminare i dati è chiamare remove() su un riferimento alla loro posizione.

Puoi anche eliminare specificando null come valore per un'altra operazione di scrittura, ad esempio set() o update(). Puoi utilizzare questa tecnica con update() per eliminare più elementi secondari in una singola chiamata API.

Ricevere un Promise

Per sapere quando i tuoi dati vengono applicati al server Firebase Realtime Database, puoi utilizzare un Promise. Sia set() che update() possono restituire un Promise che puoi utilizzare per sapere quando la scrittura viene eseguita nel database.

Scollegare gli ascoltatori

I callback vengono rimossi chiamando il metodo off() sul riferimento al database Firebase.

Puoi rimuovere un singolo ascoltatore passandolo come parametro a off(). La chiamata a off() sulla posizione senza argomenti rimuove tutti gli ascoltatori in quella posizione.

La chiamata a off() su un ascoltatore principale nonrimuove automaticamente gli ascoltatori registrati sui relativi nodi secondari.off() deve essere chiamato anche su tutti gli ascoltatori secondari per rimuovere il callback.

Salvare i dati come transazioni

Quando lavori con dati che potrebbero essere danneggiati da modifiche contemporaneamente, ad esempio contatori incrementali, puoi utilizzare un'operazione di transazione. Puoi assegnare a questa operazione una funzione di aggiornamento e un callback di completamento facoltativo. La funzione di aggiornamento prende lo stato attuale dei dati come argomento e restituisce il nuovo stato desiderato che vuoi scrivere. Se un altro client scrive nella posizione prima che il nuovo valore venga scritto correttamente, la funzione di aggiornamento viene richiamata di nuovo con il nuovo valore corrente e la scrittura viene riprovata.

Ad esempio, nell'esempio di app di social blogging, potresti consentire agli utenti di assegnare e togliere stelle ai post e tenere traccia del numero di stelle ricevute da un post come segue:

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

L'utilizzo di una transazione impedisce che i conteggi delle stelle siano errati se più utenti assegnano una stella allo stesso post contemporaneamente o se il cliente ha dati non aggiornati. Se la transazione viene rifiutata, il server restituisce il valore corrente al client, che esegue di nuovo la transazione con il valore aggiornato. L'operazione viene ripetuta finché non viene accettata o interrotta.

Incrementi atomici lato server

Nel caso d'uso riportato sopra, scriviamo due valori nel database: l'ID dell'utente che aggiunge/rimuove una stella al post e il conteggio delle stelle incrementato. Se sappiamo già che l'utente aggiunge il post ai preferiti, possiamo utilizzare un'operazione di incremento atomico anziché una transazione.

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

Questo codice non utilizza un'operazione di transazione, pertanto non viene eseguito nuovamente automaticamente se è presente un aggiornamento in conflitto. Tuttavia, poiché l'operazione di incremento avviene direttamente sul server di database, non esiste la possibilità di un conflitto.

Se vuoi rilevare e rifiutare conflitti specifici dell'applicazione, ad esempio un utente che aggiunge un post a Speciali dopo averlo già aggiunto in precedenza, devi scrivere regole di sicurezza personalizzate per questo caso d'uso.

Lavorare con i dati offline

Se un client perde la connessione di rete, l'app continuerà a funzionare correttamente.

Ogni client collegato a un database Firebase gestisce la propria versione interna di tutti i dati attivi. Quando i dati vengono scritti, vengono prima scritti in questa versione locale. Il client Firebase sincronizza quindi i dati con i server database remoto e con altri client in base al criterio "best effort".

Di conseguenza, tutte le scritture nel database attivano immediatamente gli eventi locali, prima che qualsiasi dato venga scritto sul server. Ciò significa che la tua app rimane dinamica indipendentemente dalla latenza o dalla connettività di rete.

Una volta ripristinata la connettività, l'app riceve l'insieme appropriato di eventi in modo che il client si sincronizzi con lo stato attuale del server, senza dover scrivere codice personalizzato.

Scopriremo di più sul comportamento offline in Scopri di più sulle funzionalità online e offline.

Passaggi successivi