Skip to Content
FlutterLezione 5 Accesso Ai Dati5.5 - Firebase Autenticazione e Database in tempo reale

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
💡
Tip

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 usi await prima di runApp(). Questo metodo assicura che Flutter abbia completato l’inizializzazione del motore.
  • Firebase.initializeApp() avvia Firebase e accetta come parametro le opzioni generate nel file firebase_options.dart, che viene creato automaticamente con flutterfire configure.
  • Il metodo DefaultFirebaseOptions.currentPlatform seleziona le configurazioni appropriate per la piattaforma corrente (Android, iOS, Web, ecc.).
⚠️
Warning

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:

  1. Vai nella sezione “Build” > “Realtime Database”
  2. Clicca su “Crea database”
  3. 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’utente
  • auth.token.email: email utente (se disponibile)
  • auth.token.email_verified: true se email verificata
  • auth.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 con uid=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

OggettoDescrizione
newDataRappresenta il valore che verrà scritto nel nodo se la scrittura è valida
nowTimestamp del server in millisecondi. Utile per scadenze, limiti temporali
requestOggetto 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 documento
  • allow: 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 o ownerId 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

  1. Crea un’app Flutter con Firebase
  2. Implementa registrazione e login con email/password
  3. Crea una lista di Todo che si aggiorna in tempo reale con Firestore
  4. Ogni todo deve:
    • appartenere a un utente loggato (salva uid)
    • poter essere completato e cancellato
  5. Mostra messaggi di errore e loading
  6. Proteggi i dati con regole Firebase