Firebase: Autenticazione e Database in tempo reale
1. Cos’è Firebase?
Firebase è una piattaforma Backend-as-a-Service (BaaS) sviluppata da Google, progettata per semplificare lo sviluppo di applicazioni moderne offrendo una vasta gamma di servizi cloud pronti all’uso. Di seguito approfondiamo le principali funzionalità offerte:
Autenticazione
Firebase Authentication consente di gestire facilmente l’accesso degli utenti tramite diversi provider, tra cui:
- Email e password
- Provider social (Google, Facebook, Twitter, Apple, ecc.)
- Autenticazione anonima
- Single Sign-On (SSO)
Permette la gestione sicura di registrazione, accesso, recupero password e autenticazione a più fattori, integrandosi facilmente sia con frontend che backend.
Realtime Database
Un database NoSQL basato su JSON, pensato per sincronizzare dati in tempo reale tra tutti i client collegati. Ogni modifica viene propagata istantaneamente su tutte le piattaforme connesse. Ideale per chat, giochi multiplayer, dashboard live e applicazioni dove la reattività è fondamentale.
Caratteristiche:
- Aggiornamenti real-time
- Struttura dati ad albero (simile a un oggetto JSON annidato)
- Regole di sicurezza per l’accesso e la modifica dei dati
Cloud Firestore
Un database NoSQL più avanzato, scalabile e flessibile rispetto al Realtime Database. I dati sono organizzati in collezioni e documenti, consentendo query complesse e migliori prestazioni su larga scala.
Caratteristiche:
- Supporto per query avanzate e indicizzazione
- Sincronizzazione in tempo reale
- Struttura dati gerarchica (collezioni e documenti)
- Sicurezza basata su regole flessibili
Storage
Firebase Storage permette di caricare e gestire file di grandi dimensioni come immagini, video, PDF e altri media direttamente nel cloud, utilizzando l’infrastruttura scalabile di Google Cloud Storage.
Caratteristiche:
- Caricamento e download di file
- Gestione dei permessi di accesso ai file
- Integrazione con l’autenticazione per restringere l’accesso ai soli utenti autorizzati
- Gestione automatica della scalabilità e affidabilità
Analytics
Firebase Analytics (Google Analytics for Firebase) offre una piattaforma completa per l’analisi del comportamento degli utenti nell’app. Permette di raccogliere automaticamente dati su eventi, funnel, utenti attivi, retention e molto altro.
Caratteristiche:
- Monitoraggio degli eventi personalizzati e predefiniti
- Analisi delle conversioni, funnel e pubblico
- Integrazione con altri servizi Firebase (es: Messaging e A/B testing)
- Segmentazione degli utenti per campagne mirate
Messaging
Firebase Cloud Messaging (FCM) permette di inviare notifiche push e messaggi dati ai dispositivi degli utenti in modo affidabile e gratuito.
Caratteristiche:
- Notifiche push per Android, iOS e web
- Messaggi mirati a segmenti di utenti specifici
- Integrazione con Analytics per campagne basate su eventi
- Supporto per notifiche istantanee e programmate
Hosting
Firebase Hosting consente di pubblicare e servire facilmente siti web statici e applicazioni web (SPA e PWA) tramite CDN globali, garantendo performance elevate e massima sicurezza.
Caratteristiche:
- Deployment semplice con un solo comando
- HTTPS, SSL e CDN inclusi di default
- Supporto per redirect, rewrite e custom domain
- Integrazione con Cloud Functions per backend dinamici
2. Prepara il tuo workspace
Il modo più rapido e affidabile per integrare Firebase in un progetto Flutter è utilizzare l’interfaccia a riga di comando FlutterFire CLI, che automatizza la configurazione per Android, iOS, Web, e (con limitazioni) anche Windows e macOS.
Requisiti iniziali
Prima di iniziare, assicurati di avere:
- Flutter installato correttamente (Guida ufficiale Flutter )
- Node.js installato (necessario per Firebase CLI)
- Dart SDK già incluso con Flutter
1. Installa Firebase CLI
La Firebase CLI consente di gestire progetti, deploy e risorse Firebase direttamente dal terminale.
Per tutte le piattaforme:
npm install -g firebase-tools
Verifica l’installazione:
firebase --version
Documentazione ufficiale Firebase CLI
2. Accedi al tuo account Firebase
Effettua il login dal terminale:
firebase login
Si aprirà una finestra del browser per l’autenticazione con Google.
3. Crea un nuovo progetto Flutter
Se non l’hai già fatto:
flutter create nome_progetto
cd nome_progetto
Nota: Per supportare Windows o macOS esegui anche:
Per Windows:
flutter config --enable-windows-desktop
flutter create .
Per macOS:
flutter config --enable-macos-desktop
flutter create .
4. Installa FlutterFire CLI
FlutterFire CLI consente di collegare il progetto Flutter con Firebase e genera la configurazione necessaria per tutte le piattaforme supportate.
dart pub global activate flutterfire_cli
In caso di problemi di PATH, segui le istruzioni riportate dopo il comando per aggiungere il tool ai binari globali.
Documentazione FlutterFire CLI
5. Collega Firebase al progetto Flutter
Nella root del progetto Flutter, lancia:
flutterfire configure
Questo comando:
- Ti permette di selezionare il progetto Firebase da collegare
- Configura in automatico le piattaforme (Android, iOS, Web, Windows*, macOS*)
- Genera il file
firebase_options.dart
con tutte le impostazioni necessarie
Le funzionalità desktop (Windows, macOS) sono ancora in fase sperimentale. Controlla sempre la compatibilità delle singole librerie Firebase che vuoi utilizzare. Stato supporto piattaforme
6. Risorse utili
3. Aggiunta e panoramica dei pacchetti Firebase per Flutter
Una volta configurato Firebase nel progetto Flutter, il passo successivo è aggiungere i pacchetti FlutterFire necessari. Questi pacchetti consentono l’integrazione con i vari servizi offerti da Firebase, come autenticazione, database, notifiche push, storage, analytics e altro ancora.
Esempio base in pubspec.yaml
Inizia con i pacchetti fondamentali per l’autenticazione e il database:
dependencies:
firebase_core: ^2.15.1 # Inizializzazione dei servizi Firebase
firebase_auth: ^4.7.0 # Autenticazione utenti
cloud_firestore: ^4.9.2 # Database NoSQL con sincronizzazione real-time
Dopo averli aggiunti, esegui:
flutter pub get
Per inizializzare Firebase nel codice, ricorda di chiamare Firebase.initializeApp()
nel metodo main()
.
Pacchetti principali di FlutterFire
firebase_core
Base di tutti i pacchetti. Necessario per inizializzare Firebase nella tua app Flutter.
firebase_auth
Autenticazione tramite email/password, provider esterni (Google, Facebook, Apple), login anonimo, verifica email, gestione sessioni, MFA.
cloud_firestore
Database NoSQL avanzato con collezioni/documenti, sincronizzazione in tempo reale, query complesse e scalabilità.
firebase_database
Realtime Database: ottimo per sincronizzazione semplice e veloce, con struttura a chiave/valore JSON-like.
firebase_storage
Per il caricamento, la gestione e il download di file multimediali o documenti, con permessi di accesso legati all’autenticazione.
firebase_messaging
Invio di notifiche push personalizzate a dispositivi Android, iOS o Web. Supporta notifiche in foreground, background e terminated.
firebase_analytics
Tracciamento eventi, funnel e comportamento degli utenti. Utile per ottimizzare l’esperienza utente e il marketing.
firebase_crashlytics
Monitoraggio crash in tempo reale con stack trace, contesto e gestione automatica delle segnalazioni.
firebase_remote_config
Permette di aggiornare dinamicamente il comportamento e l’aspetto dell’app (es. nascondere una sezione, cambiare colori) senza dover pubblicare nuove versioni.
firebase_performance
Analizza le performance dell’app in termini di tempi di avvio, caricamento schermate e latenze delle chiamate di rete.
firebase_app_check
Protegge le chiamate ai servizi Firebase da app non autorizzate con soluzioni come SafetyNet, App Attest o reCAPTCHA.
4. Inizializzazione di Firebase in Flutter
Una volta aggiunti i pacchetti necessari e collegato il progetto Firebase tramite flutterfire configure
, è fondamentale inizializzare Firebase all’avvio dell’applicazione. Questo processo garantisce che tutti i servizi Firebase siano attivi prima dell’uso.
L’inizializzazione avviene nel file main.dart
, ed è indispensabile farla prima di chiamare runApp()
.
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
Dettagli:
WidgetsFlutterBinding.ensureInitialized()
è necessario quando usiawait
prima dirunApp()
. Questo metodo assicura che Flutter abbia completato l’inizializzazione del motore.Firebase.initializeApp()
avvia Firebase e accetta come parametro le opzioni generate nel filefirebase_options.dart
, che viene creato automaticamente conflutterfire configure
.- Il metodo
DefaultFirebaseOptions.currentPlatform
seleziona le configurazioni appropriate per la piattaforma corrente (Android, iOS, Web, ecc.).
Se non inizializzi correttamente Firebase, qualsiasi utilizzo successivo dei suoi servizi (come Auth, Firestore o Storage) lancerà errori.
5. Autenticazione con Email e Password in Firebase
Di seguito sono riportati i principali metodi per implementare registrazione, login, logout e verifica dello stato dell’utente.
Registrazione di un nuovo utente
Future<void> registerUser(String email, String password) async {
try {
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: email,
password: password,
);
print('Registrazione completata.');
} on FirebaseAuthException catch (e) {
print('Errore durante la registrazione: ${e.message}');
}
}
Questo metodo crea un nuovo utente su Firebase con l’indirizzo email e la password forniti. Se la registrazione ha successo, l’utente viene automaticamente loggato.
Login di un utente esistente
Future<void> loginUser(String email, String password) async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
print('Login effettuato.');
} on FirebaseAuthException catch (e) {
print('Errore di login: ${e.message}');
}
}
Effettua l’autenticazione dell’utente e, se le credenziali sono corrette, restituisce un UserCredential
con i dati dell’utente.
Logout
Future<void> logout() async {
await FirebaseAuth.instance.signOut();
print('Logout completato.');
}
Il metodo signOut()
rimuove la sessione attiva. Dopo il logout, currentUser
diventerà null
.
Stato dell’utente corrente
final user = FirebaseAuth.instance.currentUser;
if (user != null) {
print('Utente loggato: ${user.email}');
} else {
print('Nessun utente attualmente loggato.');
}
Il campo currentUser
ti permette di verificare se un utente è attualmente loggato. Può essere utile per mostrare o nascondere schermate in base allo stato di autenticazione.
Autenticazione reattiva con StreamBuilder
Un modo efficace per reagire in tempo reale ai cambiamenti dello stato di autenticazione (login/logout) è utilizzare StreamBuilder
in combinazione con il metodo authStateChanges()
di FirebaseAuth:
StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasData) {
return TodoPage();
}
return LoginPage();
},
)
Spiegazione:
authStateChanges()
restituisce uno stream che emette eventi ogni volta che lo stato di autenticazione cambia (es. login, logout, registrazione).- Quando lo stream emette un valore (
hasData
), significa che l’utente è autenticato e possiamo mostrare la schermata principale (TodoPage
). - Se
hasData
è falso, viene mostrata la schermata di login. - Durante il caricamento (
ConnectionState.waiting
), viene mostrato un indicatore di caricamento.
Questo approccio è utile per mantenere la UI sincronizzata automaticamente con lo stato utente.
Buone pratiche
- Verifica sempre la validità della mail e della password prima di inviare la richiesta.
- Gestisci i casi di errore con messaggi chiari per l’utente (es. password troppo debole, utente non trovato, email già in uso).
Documentazione utile
6. Realtime Database
Il Realtime Database di Firebase è un database NoSQL in tempo reale, strutturato a oggetti JSON. Questa sezione è suddivisa in 3 parti:
- Configurazione
- Regole di sicurezza
- Lettura e scrittura dei dati da Flutter
6.1 Configurazione del Realtime Database
Per attivare il Realtime Database dalla Firebase Console:
- Vai nella sezione “Build” > “Realtime Database”
- Clicca su “Crea database”
- Seleziona la località e scegli la modalità bloccata (per sicurezza)
Una volta avviato, puoi configurare il database nella scheda Dati o Regole.
Per iniziare, puoi creare una struttura come questa:
{
"data1": {
"messaggio": "ciao",
"numero": 42
}
}
6.2 Regole di sicurezza
Le regole di sicurezza del Realtime Database controllano l’accesso in lettura e scrittura. Sono scritte in un linguaggio dichiarativo basato su condizioni.
Esempio base:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
Questo consente accesso solo agli utenti autenticati su tutto il database.
Regole su nodi specifici
Puoi anche proteggere solo un ramo:
{
"rules": {
"data1": {
".read": "auth.uid != null",
".write": "auth.uid != null"
}
}
}
Oggetto auth
Firebase mette a disposizione l’oggetto auth
:
auth.uid
: ID univoco dell’utenteauth.token.email
: email utente (se disponibile)auth.token.email_verified
: true se email verificataauth.token.<custom_claim>
: claim personalizzati, es. admin
Esempio:
{
"rules": {
"data1": {
".read": "auth.token.email_verified === true",
".write": "auth.token.admin === true"
}
}
}
Wildcard $
nelle regole
Firebase supporta le wildcard ($
) per riferirsi a rami dinamici nel database. Questo è utile per scrivere regole riutilizzabili su percorsi che cambiano in base all’utente o a un identificatore.
Esempio:
{
"rules": {
"utenti": {
"$userId": {
".read": "$userId === auth.uid",
".write": "$userId === auth.uid"
}
}
}
}
In questo caso:
- La variabile
$userId
rappresenta un identificatore dinamico (ad esempio,user123
) - Le regole permettono a ogni utente di leggere/scrivere solo il proprio nodo (
utenti/user123
per l’utente conuid=user123
)
Le wildcard possono essere annidate, e i nomi (es. $userId
) sono a tua discrezione purché preceduti da $
.
Oggetto root
nelle regole
L’oggetto root
rappresenta la radice dell’intero database e può essere usato nelle regole per accedere a qualsiasi nodo, anche al di fuori del percorso corrente. È utile quando le autorizzazioni dipendono da valori esterni al nodo corrente.
Esempio:
Struttura dati:
{
"roles": {
"user123": "admin",
"user456": "user"
},
"utenti": {
"user123": {
"nome": "Mario",
"email": "[email protected]"
},
"user456": {
"nome": "Luca",
"email": "[email protected]"
}
}
}
Regole:
{
"rules": {
"utenti": {
"$userId": {
".read": "root.child('roles').child($userId).val() === 'admin'"
}
}
}
}
In questo esempio, l’utente può leggere il proprio nodo solo se nella radice (root
) del database esiste un percorso roles/$userId
con valore admin
.
Oggetto data
nelle regole
L’oggetto data
rappresenta i dati attualmente presenti nel nodo specificato. Può essere usato per controllare valori già esistenti prima di un’operazione di lettura o scrittura.
Esempio con ruolo incorporato nel profilo utente:
Struttura dati:
{
"utenti": {
"user123": {
"nome": "Mario",
"email": "[email protected]",
"ruolo": "admin"
},
"user456": {
"nome": "Luca",
"email": "[email protected]",
"ruolo": "user"
}
}
}
Regole:
{
"rules": {
"utenti": {
"$userId": {
".read": "$userId === auth.uid || data.child('ruolo').val() === 'admin'",
".write": "$userId === auth.uid"
}
}
}
}
In questo caso:
- L’utente può leggere il proprio nodo.
- Oppure può leggere altri nodi se il valore esistente del campo
ruolo
è'admin'
.
data.child()
è molto utile per controlli basati sul contenuto già salvato nel database.
Altri oggetti utili nelle regole
Oggetto | Descrizione |
---|---|
newData | Rappresenta il valore che verrà scritto nel nodo se la scrittura è valida |
now | Timestamp del server in millisecondi. Utile per scadenze, limiti temporali |
request | Oggetto che include auth , time , e resource per avere metadati estesi |
Esempi:
Controllo email valida in newData
:
{
".validate": "newData.child('email').isString() && newData.child('email').val().matches(/^.+@.+..+$/)"
}
Verifica scadenza con now
:
{
".read": "now < data.child('scadenza').val()"
}
Controllo tramite request.time
:
{
".write": "request.time < timestamp.date(2025, 12, 31)"
}
6.3 Lettura e scrittura da Flutter
Aggiungi il pacchetto nel pubspec.yaml
dependencies:
firebase_database: ^10.3.6
Inizializzazione e scrittura
final db = FirebaseDatabase.instance.ref();
await db.child('data1').set({
'messaggio': 'ciao da Flutter',
'numero': 123
});
Lettura dei dati
final snapshot = await FirebaseDatabase.instance.ref('data1').get();
if (snapshot.exists) {
print(snapshot.value);
} else {
print('Nessun dato trovato');
}
Ascolto in tempo reale
FirebaseDatabase.instance.ref('data1').onValue.listen((event) {
final data = event.snapshot.value;
print('Dati aggiornati: $data');
});
Risorse utili
7. Firestore - Salvataggio, lettura e gestione CRUD
Firestore è un database NoSQL scalabile in tempo reale, ideale per app Flutter. Ogni documento è memorizzato in una collezione, e può essere letto, scritto, aggiornato o eliminato tramite l’SDK ufficiale.
Aggiunta di un todo
Future<void> addTodo(String title) async {
await FirebaseFirestore.instance.collection('todos').add({
'title': title,
'completed': false,
'createdAt': FieldValue.serverTimestamp(),
'uid': FirebaseAuth.instance.currentUser!.uid,
});
}
Lettura in tempo reale
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('todos')
.where('uid', isEqualTo: FirebaseAuth.instance.currentUser!.uid)
.orderBy('createdAt')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const CircularProgressIndicator();
final docs = snapshot.data!.docs;
return ListView(
children: docs.map((doc) {
final todo = doc.data() as Map<String, dynamic>;
return ListTile(
title: Text(todo['title']),
trailing: Checkbox(
value: todo['completed'],
onChanged: (val) {
FirebaseFirestore.instance
.collection('todos')
.doc(doc.id)
.update({'completed': val});
},
),
onLongPress: () => FirebaseFirestore.instance
.collection('todos')
.doc(doc.id)
.delete(),
);
}).toList(),
);
},
)
Lettura una tantum (non in tempo reale)
Future<List<Map<String, dynamic>>> getTodosOnce() async {
final snapshot = await FirebaseFirestore.instance
.collection('todos')
.where('uid', isEqualTo: FirebaseAuth.instance.currentUser!.uid)
.orderBy('createdAt')
.get();
return snapshot.docs.map((doc) => doc.data()).toList();
}
Aggiornamento di un documento
Future<void> updateTodo(String docId, bool completed) async {
await FirebaseFirestore.instance
.collection('todos')
.doc(docId)
.update({'completed': completed});
}
Eliminazione di un documento
Future<void> deleteTodo(String docId) async {
await FirebaseFirestore.instance
.collection('todos')
.doc(docId)
.delete();
}
Sovrascrittura completa con .set()
Future<void> replaceTodo(String docId, String title, bool completed) async {
await FirebaseFirestore.instance
.collection('todos')
.doc(docId)
.set({
'title': title,
'completed': completed,
'createdAt': FieldValue.serverTimestamp(),
'uid': FirebaseAuth.instance.currentUser!.uid,
});
}
Aggiornamento parziale con .set(merge: true)
Future<void> updateTitleOnly(String docId, String newTitle) async {
await FirebaseFirestore.instance
.collection('todos')
.doc(docId)
.set({'title': newTitle}, SetOptions(merge: true));
}
Rimozione di un campo
Future<void> removeCreatedAt(String docId) async {
await FirebaseFirestore.instance
.collection('todos')
.doc(docId)
.update({'createdAt': FieldValue.delete()});
}
Regole di Sicurezza in Firebase
Le regole di sicurezza di Firebase Firestore servono a proteggere l’accesso ai dati nel tuo database. Queste regole definiscono chi può leggere o scrivere su un determinato documento o collezione, e in quali condizioni.
Struttura delle regole
Le regole si scrivono in una sintassi simile a JavaScript e sono composte da:
match
: indica il percorso della collezione o del documentoallow
: specifica l’operazione permessa (read, write, create, update, delete)- una condizione booleana:
if ...
Esempio generico:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /collectionName/{docId} {
allow read, write: if <condizione>;
}
}
}
Esempio: accesso solo autenticato
match /posts/{postId} {
allow read, write: if request.auth != null;
}
Questa regola permette la lettura e scrittura solo agli utenti autenticati.
Esempio: accesso solo ai propri documenti
match /todos/{todoId} {
allow read, write: if request.auth != null && request.auth.uid == resource.data.uid;
}
Questa regola consente l’accesso solo se il documento appartiene all’utente (campo uid
).
Per la creazione di un nuovo documento, resource.data
non esiste ancora: bisogna usare request.resource.data
:
match /todos/{todoId} {
allow create: if request.auth != null && request.resource.data.uid == request.auth.uid;
allow read, update, delete: if request.auth != null && resource.data.uid == request.auth.uid;
}
Esempio: lettura pubblica, scrittura autenticata
match /gallery/{id} {
allow read: if true;
allow write: if request.auth != null;
}
Chiunque può leggere la gallery, ma solo utenti autenticati possono modificarla.
Altri esempi utili
Solo lettura per tutti:
allow read: if true;
allow write: if false;
Scrittura solo se un campo specifico ha un valore accettabile:
allow create: if request.resource.data.status in ['draft', 'published'];
Controllo sulla lunghezza di una stringa:
allow write: if request.resource.data.title.size() < 100;
Buone pratiche
- Mantieni le regole semplici e specifiche
- Testa le regole con la funzione “Simula una richiesta” da Firebase Console
- Non lasciare
read: if true
in ambienti di produzione senza controllo - Usa campi come
uid
oownerId
nei tuoi documenti per limitare gli accessi
Dove si scrivono
Nel pannello Firebase: Firestore > Regole
Puoi anche versionarle usando un file firestore.rules
nel progetto Firebase CLI.
9. Gestione degli errori
Login sicuro
try {
await loginUser(email, password);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
print('Utente non trovato');
} else if (e.code == 'wrong-password') {
print('Password errata');
}
}
Scrittura protetta
try {
await FirebaseFirestore.instance.collection('todos').add(...);
} catch (e) {
print('Errore Firestore: $e');
}
Esercizio Completo
- Crea un’app Flutter con Firebase
- Implementa registrazione e login con email/password
- Crea una lista di Todo che si aggiorna in tempo reale con Firestore
- Ogni
todo
deve:- appartenere a un utente loggato (salva
uid
) - poter essere completato e cancellato
- appartenere a un utente loggato (salva
- Mostra messaggi di errore e loading
- Proteggi i dati con regole Firebase