Salvare dati nel cloud con Cloud Firestore in Flutter
GuideIntermedio40 min Flutter 3.x

Salvare dati nel cloud con Cloud Firestore in Flutter

Cloud Firestore è il database NoSQL realtime di Firebase, perfetto per applicazioni che necessitano di sincronizzazione automatica dei dati tra dispositivi. In questo tutorial costruiremo una semplice app per gestire una lista di task (note) salvate nel cloud: vedremo come configurare il progetto, scrivere documenti, leggerli in tempo reale tramite stream e gestire aggiornamenti ed eliminazioni.

Alla fine avrai una base solida per integrare Firestore in qualsiasi progetto Flutter.

  1. 1

    Configurare Firebase e aggiungere le dipendenze

    Per prima cosa crea un progetto su Firebase Console e abilita Cloud Firestore in modalità test (per lo sviluppo).

    Installa la CLI di Firebase e FlutterFire CLI, quindi collega il progetto:

    dart pub global activate flutterfire_cli
    flutterfire configure
    

    Questo comando genera il file firebase_options.dart. Aggiungi infine le dipendenze al pubspec.yaml.

    dependencies:
      flutter:
        sdk: flutter
      firebase_core: ^2.27.0
      cloud_firestore: ^4.15.0

    Risultato atteso

    Il progetto Flutter è collegato a Firebase e le dipendenze sono installate con `flutter pub get`.

  2. 2

    Inizializzare Firebase all'avvio dell'app

    Prima di usare Firestore dobbiamo inizializzare Firebase nel metodo main. Ricordati di rendere main asincrono e di chiamare WidgetsFlutterBinding.ensureInitialized().

    import 'package:flutter/material.dart';
    import 'package:firebase_core/firebase_core.dart';
    import 'firebase_options.dart';
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
      );
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Firestore Tasks',
          theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
          home: const TasksPage(),
        );
      }
    }

    Risultato atteso

    L'app si avvia senza errori e Firebase risulta inizializzato correttamente.

  3. 3

    Creare un servizio per accedere a Firestore

    Per mantenere il codice pulito incapsuliamo le operazioni su Firestore in una classe TaskService. Lavoreremo su una collection chiamata tasks, dove ogni documento ha i campi title (testo) e done (booleano).

    import 'package:cloud_firestore/cloud_firestore.dart';
    
    class TaskService {
      final _collection = FirebaseFirestore.instance.collection('tasks');
    
      // Stream in tempo reale ordinato per data di creazione
      Stream<QuerySnapshot<Map<String, dynamic>>> watchTasks() {
        return _collection.orderBy('createdAt', descending: true).snapshots();
      }
    
      Future<void> addTask(String title) {
        return _collection.add({
          'title': title,
          'done': false,
          'createdAt': FieldValue.serverTimestamp(),
        });
      }
    
      Future<void> toggleDone(String id, bool done) {
        return _collection.doc(id).update({'done': done});
      }
    
      Future<void> deleteTask(String id) {
        return _collection.doc(id).delete();
      }
    }

    Risultato atteso

    Hai una classe riutilizzabile con i metodi per leggere, aggiungere, aggiornare ed eliminare i task.

  4. 4

    Mostrare i dati in tempo reale con StreamBuilder

    Usiamo uno StreamBuilder collegato al metodo watchTasks(). Ogni volta che un documento cambia su Firestore, la UI si aggiorna automaticamente senza dover ricaricare la pagina.

    class TasksPage extends StatelessWidget {
      const TasksPage({super.key});
    
      @override
      Widget build(BuildContext context) {
        final service = TaskService();
        return Scaffold(
          appBar: AppBar(title: const Text('Le mie attività')),
          body: StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
            stream: service.watchTasks(),
            builder: (context, snapshot) {
              if (snapshot.hasError) {
                return const Center(child: Text('Errore nel caricamento'));
              }
              if (!snapshot.hasData) {
                return const Center(child: CircularProgressIndicator());
              }
              final docs = snapshot.data!.docs;
              if (docs.isEmpty) {
                return const Center(child: Text('Nessuna attività'));
              }
              return ListView.builder(
                itemCount: docs.length,
                itemBuilder: (context, index) {
                  final doc = docs[index];
                  final data = doc.data();
                  return CheckboxListTile(
                    title: Text(data['title'] ?? ''),
                    value: data['done'] ?? false,
                    onChanged: (v) => service.toggleDone(doc.id, v ?? false),
                    secondary: IconButton(
                      icon: const Icon(Icons.delete_outline),
                      onPressed: () => service.deleteTask(doc.id),
                    ),
                  );
                },
              );
            },
          ),
        );
      }
    }

    Risultato atteso

    La lista mostra i task presenti su Firestore e si aggiorna in tempo reale.

  5. 5

    Aggiungere nuovi task con un dialog

    Aggiungiamo un FloatingActionButton che apre un dialog per inserire il titolo di un nuovo task. Al salvataggio chiamiamo addTask, e grazie allo stream la lista si aggiorna da sola.

    // Aggiungi questo al Scaffold della TasksPage
    floatingActionButton: FloatingActionButton(
      onPressed: () => _showAddDialog(context, service),
      child: const Icon(Icons.add),
    ),
    
    // Metodo helper
    Future<void> _showAddDialog(BuildContext context, TaskService service) async {
      final controller = TextEditingController();
      await showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: const Text('Nuova attività'),
          content: TextField(
            controller: controller,
            autofocus: true,
            decoration: const InputDecoration(hintText: 'Cosa devi fare?'),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('Annulla'),
            ),
            FilledButton(
              onPressed: () {
                final text = controller.text.trim();
                if (text.isNotEmpty) {
                  service.addTask(text);
                }
                Navigator.pop(context);
              },
              child: const Text('Salva'),
            ),
          ],
        ),
      );
    }

    Risultato atteso

    Premendo il FAB puoi inserire un nuovo task che appare immediatamente nella lista e viene salvato nel cloud.

  6. 6

    Configurare le regole di sicurezza

    La modalità test scade dopo 30 giorni e lascia il database aperto a tutti. Prima di andare in produzione devi impostare regole di sicurezza adeguate. Se la tua app usa l'autenticazione, limita l'accesso agli utenti loggati.

    Nel tab Rules della console Firestore inserisci ad esempio:

    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /tasks/{document} {
          // Consenti la lettura/scrittura solo a utenti autenticati
          allow read, write: if request.auth != null;
        }
      }
    }

    Risultato atteso

    Il database non è più accessibile pubblicamente: solo gli utenti autenticati possono leggere e scrivere i task.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!