(Facoltativo) Prototipo e test con Firebase Local Emulator Suite
Prima di parlare di come la tua app legge e scrive su Realtime Database, introduciamo un insieme di strumenti che puoi utilizzare per prototipare e testare la funzionalità Realtime Database: Firebase Local Emulator Suite. Se stai provando diversi modelli di dati, ottimizzando le regole di sicurezza o cercando il modo più conveniente per interagire con il backend, poter lavorare in locale senza implementare servizi live può essere un'ottima idea.
Un emulatore Realtime Database fa parte di Local Emulator Suite, che consente alla tua app di interagire con i contenuti e la configurazione del database emulato, nonché, facoltativamente, con le risorse del progetto emulato (funzioni, altri database e regole di sicurezza).
L'utilizzo dell'emulatore Realtime Database prevede pochi passaggi:
- Aggiungendo una riga di codice alla configurazione di test dell'app per connettersi all'emulatore.
- Dalla radice della directory del progetto locale, esegui
firebase emulators:start
. - Effettuare chiamate dal codice prototipo della tua app utilizzando un SDK della piattaforma Realtime Database come di consueto o utilizzando l'API REST Realtime Database.
È disponibile una procedura dettagliata che coinvolge Realtime Database e Cloud Functions. Dai un'occhiata anche all'introduzione di Local Emulator Suite.
Recuperare un riferimento al database
Per leggere o scrivere dati dal database, devi disporre di un'istanza di firebase.database.Reference
:
Web
import { getDatabase } from "firebase/database"; const database = getDatabase();
Web
var database = firebase.database();
Scrivi dati
Questo documento tratta le nozioni di base sul recupero dei dati e su come ordinarli e filtrarli.
I dati Firebase vengono recuperati collegando un listener asincrono a un
firebase.database.Reference
. Il listener 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 tutti i dati esistenti nel percorso. Ad esempio, un'applicazione di social
blogging potrebbe aggiungere un utente con set()
nel seguente modo:
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 tutti i nodi secondari.
Lettura di dati
Ascolta gli eventi di valore
Per leggere i dati in un percorso e rilevare le modifiche, utilizza onValue()
per osservare
gli eventi. Puoi utilizzare questo evento per leggere snapshot statici dei contenuti in un determinato percorso, così come esistevano al momento dell'evento. Questo metodo
viene attivato una volta quando il listener è collegato e di nuovo ogni volta che i dati, inclusi i figli, cambiano. Alla 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()
.
Il seguente esempio mostra un'applicazione di blogging sociale che recupera il conteggio delle 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
che contiene i dati nella posizione specificata
nel database al momento dell'evento. Puoi recuperare i dati
in snapshot
con il metodo val()
.
Lettura dei dati una sola 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 eventi di 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 la migliore esperienza quando vanno 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 eseguirà il probing della cache di archiviazione locale e restituirà un errore se il valore non viene ancora trovato.
L'utilizzo non necessario di get()
può aumentare l'utilizzo della larghezza di banda e causare una perdita di
rendimento, che può essere evitata utilizzando un listener 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); });
Lettura dei dati una sola 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 ottenere immediatamente i dati dalla cache del disco locale.
Questo è utile per i dati che devono essere caricati una sola volta e non dovrebbero cambiare frequentemente o richiedere un ascolto attivo. Ad esempio, l'app di blogging 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 dei dati
Aggiornare campi specifici
Per scrivere contemporaneamente a 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 migliorare la scalabilità, puoi aggiornare tutte le istanze di questi dati utilizzando la distribuzione dei dati.
Ad esempio, un'app di social blogging potrebbe creare un post e aggiornarlo contemporaneamente al feed delle attività recenti e al feed delle attività dell'utente che ha pubblicato il post utilizzando un codice simile a 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 all'indirizzo /posts/$postid
e recuperare contemporaneamente la chiave. La chiave può
essere utilizzata per creare una seconda voce nei post
dell'utente all'indirizzo /user-posts/$userid/$postid
.
Utilizzando questi percorsi, puoi eseguire aggiornamenti simultanei a più posizioni nell'albero JSON con una singola chiamata a update()
, ad esempio come questo esempio 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 gli aggiornamenti non vanno a buon fine.
Aggiungere un callback di completamento
Se vuoi sapere quando sono stati salvati i dati, puoi aggiungere un
callback di completamento. Sia set()
sia 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, alla funzione di callback viene passato un
oggetto di errore che indica il motivo del problema.
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
posizione di questi dati.
Puoi anche eliminare specificando null
come valore per un'altra operazione di scrittura
come set()
o update()
. Puoi utilizzare questa tecnica
con update()
per eliminare più figli in una singola chiamata API.
Ricevere un Promise
Per sapere quando i tuoi dati vengono inviati 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.
Stacca i listener
I callback vengono rimossi chiamando il metodo off()
sul riferimento al database Firebase.
Puoi rimuovere un singolo listener passandolo come parametro a off()
.
La chiamata off()
nella posizione senza argomenti rimuove tutti gli ascoltatori in quella
posizione.
La chiamata di off()
su un listener genitore non
rimuove automaticamente i listener registrati nei relativi nodi secondari;
off()
deve essere chiamato anche su tutti i listener secondari
per rimuovere il callback.
Salvare i dati come transazioni
Quando lavori con dati che potrebbero essere danneggiati da modifiche simultanee, come i 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 accetta 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 chiamata di nuovo con il nuovo valore corrente e la scrittura viene ritentata.
Ad esempio, nell'app di social blogging di esempio, potresti consentire agli utenti di aggiungere e rimuovere le stelle dai post e tenere traccia di quante stelle ha ricevuto 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 il conteggio delle stelle sia errato se più utenti aggiungono una stella allo stesso post contemporaneamente o se il client disponeva di dati obsoleti. Se la transazione viene rifiutata, il server restituisce il valore corrente al client, che esegue nuovamente la transazione con il valore aggiornato. Questa operazione si ripete finché la transazione non viene accettata o non viene interrotta.
Incrementi lato server atomici
Nello scenario d'uso riportato sopra, scriviamo due valori nel database: l'ID dell'utente che aggiunge/rimuove la stella dal post e il conteggio delle stelle incrementato. Se sappiamo già che l'utente ha aggiunto 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 in caso di aggiornamento in conflitto. Tuttavia, poiché l'operazione di incremento viene eseguita 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 una stella a un post a cui l'aveva già aggiunta, devi scrivere regole di sicurezza personalizzate per questo caso d'uso.
Lavorare con i dati offline
Se un client perde la connessione di rete, la tua app continuerà a funzionare correttamente.
Ogni client connesso a un database Firebase mantiene la propria versione interna di tutti i dati attivi. Quando i dati vengono scritti, vengono scritti prima in questa versione locale. Il client Firebase sincronizza quindi i dati con i server di database remoti e con altri client in base al "miglior sforzo".
Di conseguenza, tutte le scritture nel database attivano immediatamente gli eventi locali, prima che i dati vengano scritti sul server. Ciò significa che la tua app rimane reattiva indipendentemente dalla latenza o dalla connettività di rete.
Una volta ripristinata la connettività, la tua app riceve il set appropriato di eventi in modo che il client si sincronizzi con lo stato attuale del server, senza dover scrivere codice personalizzato.
Parleremo più nel dettaglio del comportamento offline in Scopri di più sulle funzionalità online e offline.