Firebase Realtime Database è un database JSON cloud in tempo reale lanciato da Google nel 2012 per applicazioni mobili e web. Tutti i dati sono memorizzati in un unico grande albero JSON e sincronizzati tra i client connessi in tempo reale tramite connessione WebSocket. Secondo la documentazione ufficiale Firebase, 2025, Realtime Database può gestire fino a 200.000 connessioni simultanee e supporta fino a 1.000 scritture contemporanee al secondo. Il database non richiede infrastruttura server e fornisce SDK per iOS, Android, Web e piattaforme server.
Punti chiave
Firebase Realtime Database è un database NoSQL cloud che memorizza e sincronizza i dati in tempo reale tra tutti i client connessi. Lanciato nel 2012 come Firebase (prima dell'acquisizione da parte di Google), è diventato il primo database cloud in tempo reale per sviluppatori mobili. I dati sono rappresentati in formato JSON e organizzati in un albero gerarchico, dove ogni nodo ha un percorso univoco.
Il valore principale di Realtime Database è la sincronizzazione integrata. Quando un'app modifica i dati su qualsiasi dispositivo, tutti gli altri client connessi ricevono immediatamente l'aggiornamento tramite una connessione persistente. Ciò elimina la necessità per lo sviluppatore di implementare un proprio meccanismo di sincronizzazione, server WebSocket o API REST per il trasferimento dei dati tra client.
Il database fornisce SDK per tutte le principali piattaforme: Android (Java, Kotlin), iOS (Swift, Objective-C), Web (JavaScript) e ambienti server tramite Admin SDK. Secondo Google, Realtime Database è utilizzato in oltre 1,5 milioni di progetti Firebase attivi in tutto il mondo. Nonostante l'arrivo del più moderno Firestore, Realtime Database rimane una scelta popolare per progetti con strutture dati semplici.
A differenza dei database relazionali, Realtime Database non utilizza tabelle e righe. Tutti i dati sono un unico albero JSON che assomiglia a oggetti JavaScript annidati. Ad esempio, per memorizzare utenti e i loro messaggi, viene creata una gerarchia: users/userId/name e messages/messageId/text. Ogni percorso nell'albero è una stringa e i dati sono accessibili direttamente tramite questo percorso.
{
"users": {
"user1": {
"name": "Ivan Petrov",
"email": "ivan@example.com"
},
"user2": {
"name": "Maria Sokolova",
"email": "maria@example.com"
}
},
"messages": {
"-Nabc123": {
"text": "Ciao!",
"userId": "user1"
}
}
}
Una caratteristica importante è che l'annidamento profondo influisce sulle prestazioni. Quando un'app legge dati a un determinato percorso, carica tutti i nodi figli di quel percorso. Pertanto, si consiglia di progettare la struttura dei dati il più piatta possibile, evitando annidamenti superiori a 3-4 livelli. Per aggirare questo problema, si utilizza la denormalizzazione dei dati — duplicare le informazioni in diversi nodi dell'albero.
Realtime Database e Firestore sono spesso confrontati come due database cloud in tempo reale di Google. La scelta tra di essi dipende dai requisiti specifici del progetto: complessità delle query, coerenza richiesta e carico previsto. Comprendere i punti di forza di ciascun database aiuta a prendere la decisione architettonica corretta.
Il principale vantaggio di Realtime Database è la bassa latenza di sincronizzazione. Poiché tutti i dati sono memorizzati in un unico albero JSON senza ulteriori livelli di astrazione, la sincronizzazione è più veloce rispetto a Firestore. Per le applicazioni dove la velocità di consegna degli aggiornamenti è critica (chat, giochi online, sistemi di editing collaborativo), Realtime Database potrebbe essere la scelta più adatta.
Realtime Database è più adatto per scenari con strutture dati semplici e alta frequenza di aggiornamento. Esempi tipici: chat, like in tempo reale, indicatori di digitazione, stati di presenza degli utenti. È anche una buona scelta per prototipi e progetti con budget limitato, poiché il prezzo si basa sul volume dei dati, non sul numero di operazioni.
D'altra parte, per applicazioni con query complesse (filtraggio per più campi, ordinamento, aggregazione), Firestore offre capacità molto più potenti. Realtime Database supporta solo il filtraggio per un parametro e non può ordinare i risultati su più campi contemporaneamente. Se un progetto prevede analisi dati complesse lato client, Firestore sarà una scelta più pratica.
Realtime Database utilizza una connessione WebSocket persistente per la sincronizzazione bidirezionale dei dati. Quando un client chiama setValue o updateChildren su un percorso specifico, i dati vengono inviati al server Firebase tramite il canale aperto. Il server applica le modifiche e distribuisce gli aggiornamenti a tutti i client iscritti entro millisecondi. Ogni connessione è identificata da una chiave di sessione univoca.
Il meccanismo di iscrizione funziona tramite listener. Uno sviluppatore può iscriversi alle modifiche su un nodo specifico (addListenerForSingleValueEvent) o ricevere aggiornamenti continui (addValueEventListener). Ogni volta che i dati cambiano, viene attivato il callback onDataChange con un'istantanea completa dei dati al percorso specificato. Questo differisce da Firestore, dove vengono ricevuti solo i documenti modificati — in Realtime Database vengono sempre caricati tutti i dati del nodo.
Realtime Database supporta la modalità offline su Android e iOS tramite cache su disco. L'SDK mantiene una copia locale dei dati e continua a elaborare le operazioni di scrittura in assenza di rete. Quando la connessione viene ripristinata, tutte le modifiche accumulate vengono inviate al server. Per la risoluzione dei conflitti viene utilizzata la strategia last-write-wins, ma gli sviluppatori possono implementare logica personalizzata tramite ServerValue.TIMESTAMP per la risoluzione delle collisioni.
val database = FirebaseDatabase.getInstance()
val myRef = database.getReference("messages")
// Scrittura dati
myRef.push().setValue(
hashMapOf(
"text" to "Nuovo messaggio",
"timestamp" to ServerValue.TIMESTAMP
)
)
// Lettura con aggiornamenti continui
myRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val data = snapshot.getValue()
Log.d("TAG", "Dati: $data")
}
override fun onCancelled(error: DatabaseError) {
Log.w("TAG", "Errore: ${error.message}")
}
})
Per ottimizzare il traffico e le prestazioni, si consiglia di utilizzare child listener anziché value listener per monitorare le modifiche a nodi figli specifici. ChildEventListener fornisce callback separati per aggiunta, modifica, rimozione e spostamento di elementi figli, consentendo un controllo più preciso degli aggiornamenti dell'interfaccia ed evitando di ridisegnare tutti gli elementi dell'elenco a ogni modifica dei dati.
Realtime Database utilizza un linguaggio di regole dichiarativo per il controllo dell'accesso ai dati. Le regole descrivono chi può leggere e scrivere dati a ogni percorso dell'albero JSON. Vengono verificate sul server Firebase prima di ogni richiesta e non richiedono logica lato server per l'autorizzazione. Le regole supportano variabili, oggetti integrati e funzioni per una configurazione flessibile dell'accesso.
Per impostazione predefinita, l'accesso al database è negato per tutti gli utenti. Lo sviluppatore apre gradualmente l'accesso utilizzando le regole ".read" e ".write" a vari livelli dell'albero. Le condizioni possono verificare l'autenticazione tramite la variabile auth, il tipo di richiesta (lettura/scrittura) e i dati esistenti tramite l'oggetto data. Inoltre, le regole supportano la validazione dei dati scritti tramite l'oggetto newData.
{
"rules": {
"users": {
"$uid": {
// Solo il proprietario può leggere i propri dati
".read": "$uid === auth.uid",
// Solo il proprietario può scrivere
".write": "$uid === auth.uid",
// Validazione dei campi in scrittura
".validate": "newData.hasChildren(['name', 'email'])"
}
},
"messages": {
// Qualsiasi utente autenticato può leggere
".read": "auth !== null",
// Solo utente autenticato può scrivere
".write": "auth !== null",
".indexOn": ["timestamp"]
}
}
}
Le regole supportano anche l'indicizzazione dei dati tramite la direttiva ".indexOn". Senza di essa, le query con ordinamento (orderByChild) verranno rifiutate o eseguite in modo inefficiente. Gli indici sono specificati per ogni percorso in cui viene eseguito l'ordinamento per un campo specifico. Le regole sono a cascata: le regole più profonde sovrascrivono le regole padre e se l'accesso non è definito a un certo livello, viene considerato consentito o negato in base alla regola padre.
Realtime Database supporta cinque tipi di dati: String, Number, Boolean, Map (oggetto) e List (array). La profondità di annidamento è limitata a 32 livelli e la dimensione massima di un singolo nodo non deve superare 256 MB. Per un lavoro efficiente con il database, si consiglia di progettare una struttura dati piatta e utilizzare la denormalizzazione per evitare query profonde che caricano grandi quantità di dati.
Consideriamo un esempio pratico di integrazione di Realtime Database in un'applicazione Android per stati utente (online/offline). L'applicazione mostrerà un elenco di utenti con il loro stato corrente, aggiornato in tempo reale. Per la dimostrazione vengono utilizzati Firebase Authentication per l'identificazione degli utenti e coroutine per operazioni asincrone.
Per iniziare, aggiungi la dipendenza firebase-database-ktx al file build.gradle del modulo dell'app. La versione della libreria è gestita tramite Firebase BoM per garantire la compatibilità di tutti i componenti. Dopo aver aggiunto la dipendenza, Firebase deve essere inizializzato nella classe Application o tramite inizializzazione lazy nel ViewModel.
dependencies {
implementation platform("com.google.firebase:firebase-bom:33.0.0")
implementation "com.google.firebase:firebase-database-ktx"
implementation "com.google.firebase:firebase-auth-ktx"
}
Dopo la configurazione, viene creato un repository per lavorare con gli utenti. Ogni utente è rappresentato da un nodo nell'albero /users/{uid} con i campi name, email e status. Per il monitoraggio dello stato viene utilizzato onDisconnect — un meccanismo speciale di Firebase che esegue automaticamente un'operazione di scrittura quando la connessione del client viene interrotta. Ciò garantisce che lo stato dell'utente passi a "offline" alla chiusura dell'app o alla perdita di rete senza codice aggiuntivo sul client.
class PresenceRepository {
private val database = FirebaseDatabase.getInstance()
private val auth = FirebaseAuth.getInstance()
private val presenceRef = database
.getReference("presence")
fun trackPresence() {
val uid = auth.currentUser?.uid ?: return
val userRef = presenceRef.child(uid)
userRef.onDisconnect().setValue("offline")
userRef.setValue("online")
}
fun getPresenceStream(): Flow<Map<String, String>> =
presenceRef.snapshotFlow()
.map { snapshot ->
(snapshot.value as? Map<*, *>)
?.mapKeys { it.key.toString() }
?.mapValues { it.value.toString() }
?: emptyMap()
}
}
L'elemento chiave dell'esempio è onDisconnect. Questo meccanismo consente di impostare un'operazione di scrittura che verrà eseguita sul server quando la connessione del client viene interrotta. In questo caso, quando l'utente si disconnette, il suo stato viene automaticamente impostato su "offline" senza dover gestire l'evento di chiusura dell'app. Se l'app si blocca, Firebase stesso eseguirà l'operazione onDisconnect e gli altri utenti vedranno lo stato corretto.
Domande frequenti
Realtime Database memorizza i dati in un unico albero JSON e fornisce una latenza di sincronizzazione inferiore. Firestore utilizza raccolte di documenti, supporta query complesse e coerenza forte. Realtime Database è migliore per chat semplici e stati, Firestore è migliore per applicazioni con strutture dati complesse e analisi.
La dimensione massima di un singolo nodo Realtime Database è di 256 MB. La profondità di annidamento è limitata a 32 livelli. Per un progetto Firebase, è possibile creare più istanze Realtime Database (fino a 5 sul piano Spark e fino a 100 sul piano Blaze), consentendo di distribuire i dati tra diverse istanze.
Realtime Database si integra con Firebase Authentication. La variabile auth contenente l'uid dell'utente autenticato è disponibile nelle regole di sicurezza. Gli sviluppatori possono limitare l'accesso a livello di singoli nodi dell'albero JSON verificando l'uid del proprietario dei dati. Gli utenti anonimi e non autenticati hanno auth = null.
Sì, Realtime Database supporta le transazioni tramite il metodo runTransaction. Una transazione garantisce l'atomicità dell'operazione di lettura-modifica-scrittura per un singolo nodo. Quando si verificano modifiche concorrenti, la transazione viene ripetuta con i dati correnti. Ciò è utile per contatori, valutazioni e altri scenari in cui la coerenza dei dati è importante.
Sì, Realtime Database supporta la modalità offline su Android e iOS. L'SDK memorizza nella cache i dati localmente e continua a elaborare le operazioni di scrittura senza rete. Quando la connessione viene ripristinata, tutte le modifiche accumulate vengono sincronizzate con il server. Per attivare la modalità offline, utilizzare il metodo keepSynced(true) sul nodo desiderato.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche