Salvare dati strutturati in Flutter con il database SQLite e sqflite
GuideIntermedio40 min Flutter 3.x

Salvare dati strutturati in Flutter con il database SQLite e sqflite

Quando un'app Flutter deve gestire molti dati strutturati e relazioni, shared_preferences non basta più. In questi casi un vero database relazionale come SQLite è la scelta ideale.

In questo tutorial vedremo come usare il pacchetto sqflite per creare un database locale, definire una tabella e implementare le operazioni CRUD (Create, Read, Update, Delete) seguendo un pattern pulito e riutilizzabile.

Costruiremo un piccolo gestore di note persistenti.

  1. 1

    Aggiungere le dipendenze

    Aggiungiamo sqflite per il database e path per costruire in modo sicuro il percorso del file su ogni piattaforma.

    Esegui da terminale:

    flutter pub add sqflite path
    

    In alternativa modifica manualmente il file pubspec.yaml come mostrato nello snippet e poi lancia flutter pub get.

    dependencies:
      flutter:
        sdk: flutter
      sqflite: ^2.3.0
      path: ^1.9.0

    Risultato atteso

    Le dipendenze sqflite e path risultano installate senza errori.

  2. 2

    Creare il modello dati Note

    Definiamo una classe Note con i metodi toMap e fromMap per convertire tra oggetto Dart e riga del database. SQLite memorizza valori semplici, quindi mappiamo ogni campo a una colonna.

    Usiamo un id nullable perché viene generato automaticamente dal database all'inserimento.

    class Note {
      final int? id;
      final String title;
      final String content;
      final DateTime createdAt;
    
      Note({
        this.id,
        required this.title,
        required this.content,
        required this.createdAt,
      });
    
      Map<String, Object?> toMap() {
        return {
          'id': id,
          'title': title,
          'content': content,
          'created_at': createdAt.toIso8601String(),
        };
      }
    
      factory Note.fromMap(Map<String, Object?> map) {
        return Note(
          id: map['id'] as int?,
          title: map['title'] as String,
          content: map['content'] as String,
          createdAt: DateTime.parse(map['created_at'] as String),
        );
      }
    }

    Risultato atteso

    La classe Note è pronta per essere serializzata e deserializzata dal database.

  3. 3

    Inizializzare il database e creare la tabella

    Creiamo una classe DatabaseHelper che gestisce l'apertura del database come singleton. Il database viene aperto una sola volta e riutilizzato.

    Nella callback onCreate definiamo lo schema della tabella notes. Il parametro version è importante: se in futuro modifichi lo schema potrai gestire le migrazioni con onUpgrade.

    import 'package:path/path.dart';
    import 'package:sqflite/sqflite.dart';
    
    class DatabaseHelper {
      DatabaseHelper._privateConstructor();
      static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
    
      static Database? _database;
    
      Future<Database> get database async {
        _database ??= await _initDatabase();
        return _database!;
      }
    
      Future<Database> _initDatabase() async {
        final dbPath = await getDatabasesPath();
        final path = join(dbPath, 'notes.db');
    
        return openDatabase(
          path,
          version: 1,
          onCreate: _onCreate,
        );
      }
    
      Future<void> _onCreate(Database db, int version) async {
        await db.execute('''
          CREATE TABLE notes(
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            content TEXT NOT NULL,
            created_at TEXT NOT NULL
          )
        ''');
      }
    }

    Risultato atteso

    Al primo accesso viene creato il file notes.db con la tabella notes.

  4. 4

    Implementare le operazioni CRUD

    Creiamo un NoteRepository che espone metodi chiari per inserire, leggere, aggiornare ed eliminare le note. Questo separa la logica di accesso ai dati dal resto dell'app.

    Nota l'uso di conflictAlgorithm per la insert e di whereArgs per evitare SQL injection.

    class NoteRepository {
      final DatabaseHelper _dbHelper = DatabaseHelper.instance;
    
      Future<int> insertNote(Note note) async {
        final db = await _dbHelper.database;
        return db.insert(
          'notes',
          note.toMap()..remove('id'),
          conflictAlgorithm: ConflictAlgorithm.replace,
        );
      }
    
      Future<List<Note>> getAllNotes() async {
        final db = await _dbHelper.database;
        final maps = await db.query('notes', orderBy: 'created_at DESC');
        return maps.map((m) => Note.fromMap(m)).toList();
      }
    
      Future<int> updateNote(Note note) async {
        final db = await _dbHelper.database;
        return db.update(
          'notes',
          note.toMap(),
          where: 'id = ?',
          whereArgs: [note.id],
        );
      }
    
      Future<int> deleteNote(int id) async {
        final db = await _dbHelper.database;
        return db.delete('notes', where: 'id = ?', whereArgs: [id]);
      }
    }

    Risultato atteso

    Il repository permette di eseguire tutte le operazioni CRUD sulle note.

  5. 5

    Mostrare e gestire le note nella UI

    Colleghiamo il repository a una semplice schermata. Carichiamo le note in initState, le mostriamo in una ListView e aggiungiamo un pulsante per inserire una nota di esempio.

    Dopo ogni operazione di scrittura ricarichiamo la lista per aggiornare l'interfaccia.

    class NotesScreen extends StatefulWidget {
      const NotesScreen({super.key});
    
      @override
      State<NotesScreen> createState() => _NotesScreenState();
    }
    
    class _NotesScreenState extends State<NotesScreen> {
      final NoteRepository _repo = NoteRepository();
      List<Note> _notes = [];
    
      @override
      void initState() {
        super.initState();
        _loadNotes();
      }
    
      Future<void> _loadNotes() async {
        final notes = await _repo.getAllNotes();
        setState(() => _notes = notes);
      }
    
      Future<void> _addSampleNote() async {
        await _repo.insertNote(Note(
          title: 'Nota ${_notes.length + 1}',
          content: 'Contenuto di esempio',
          createdAt: DateTime.now(),
        ));
        await _loadNotes();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Le mie note')),
          body: ListView.builder(
            itemCount: _notes.length,
            itemBuilder: (context, index) {
              final note = _notes[index];
              return ListTile(
                title: Text(note.title),
                subtitle: Text(note.content),
                trailing: IconButton(
                  icon: const Icon(Icons.delete),
                  onPressed: () async {
                    await _repo.deleteNote(note.id!);
                    await _loadNotes();
                  },
                ),
              );
            },
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _addSampleNote,
            child: const Icon(Icons.add),
          ),
        );
      }
    }

    Risultato atteso

    L'app mostra l'elenco delle note salvate e permette di aggiungerle ed eliminarle in modo persistente.

  6. 6

    Verificare la persistenza dei dati

    Per confermare che SQLite memorizzi davvero i dati su disco:

    1. Avvia l'app e crea alcune note con il pulsante +.
    2. Chiudi completamente l'app (non solo in background).
    3. Riaprila: le note create devono essere ancora presenti.

    A differenza di una lista in memoria, i dati sopravvivono al riavvio perché sono salvati nel file notes.db.

    Suggerimento: se modifichi lo schema della tabella durante lo sviluppo, disinstalla l'app dal dispositivo oppure incrementa il parametro version e implementa onUpgrade, altrimenti vedrai errori di colonna mancante.

    Risultato atteso

    Le note restano salvate anche dopo la chiusura e la riapertura dell'app, confermando la persistenza tramite SQLite.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!