Perché Drift per la persistenza locale

Quando un'applicazione Flutter ha bisogno di salvare dati strutturati in locale — pensiamo a una to-do list offline, a una cache complessa o a un'app di tracking — SharedPreferences non basta. In questi casi serve un vero database SQLite, e Drift (l'evoluzione di Moor) è oggi una delle scelte più solide.

Drift offre tre vantaggi chiave:

  • Type-safety: le query vengono validate a tempo di compilazione, niente più errori SQL scoperti a runtime.
  • Reattività: puoi osservare le query come Stream, aggiornando automaticamente la UI quando i dati cambiano.
  • Cross-platform: funziona su mobile, desktop e web.

Installazione

Aggiungi le dipendenze al pubspec.yaml:

dependencies:
  drift: ^2.18.0
  sqlite3_flutter_libs: ^0.5.0
  path_provider: ^2.1.0
  path: ^1.9.0

dev_dependencies:
  drift_dev: ^2.18.0
  build_runner: ^2.4.0

Definire una tabella

In Drift le tabelle sono classi Dart. Definiamo una tabella Todos:

import 'package:drift/drift.dart';

class Todos extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get title => text().withLength(min: 1, max: 100)();
  TextColumn get description => text().nullable()();
  BoolColumn get completed => boolean().withDefault(const Constant(false))();
  DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
}

Creare il database

La classe del database collega le tabelle e gestisce la connessione:

import 'dart:io';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;

part 'database.g.dart';

@DriftDatabase(tables: [Todos])
class AppDatabase extends _$AppDatabase {
  AppDatabase() : super(_openConnection());

  @override
  int get schemaVersion => 1;
}

LazyDatabase _openConnection() {
  return LazyDatabase(() async {
    final dir = await getApplicationDocumentsDirectory();
    final file = File(p.join(dir.path, 'app.db'));
    return NativeDatabase.createInBackground(file);
  });
}

Genera il codice con:

dart run build_runner build

Operazioni CRUD

Drift genera automaticamente le classi dei dati. Ecco i metodi principali da aggiungere alla classe AppDatabase:

// Lettura reattiva: la UI si aggiorna da sola
Stream<List<Todo>> watchAllTodos() => select(todos).watch();

// Inserimento
Future<int> addTodo(TodosCompanion entry) => into(todos).insert(entry);

// Aggiornamento
Future<bool> updateTodo(Todo todo) => update(todos).replace(todo);

// Eliminazione
Future<int> deleteTodo(int id) =>
    (delete(todos)..where((t) => t.id.equals(id))).go();

Per inserire un nuovo elemento si usa il Companion, che distingue i campi presenti da quelli assenti:

await db.addTodo(
  TodosCompanion.insert(
    title: 'Studiare Drift',
    description: const Value('Leggere la documentazione'),
  ),
);

Mostrare i dati con StreamBuilder

Grazie alla reattività, basta uno StreamBuilder per avere una lista sempre aggiornata:

StreamBuilder<List<Todo>>(
  stream: db.watchAllTodos(),
  builder: (context, snapshot) {
    if (!snapshot.hasData) {
      return const Center(child: CircularProgressIndicator());
    }
    final todos = snapshot.data!;
    return ListView.builder(
      itemCount: todos.length,
      itemBuilder: (context, i) {
        final todo = todos[i];
        return CheckboxListTile(
          title: Text(todo.title),
          value: todo.completed,
          onChanged: (val) => db.updateTodo(
            todo.copyWith(completed: val ?? false),
          ),
        );
      },
    );
  },
)

Gestire le migrazioni

Quando lo schema cambia, incrementa schemaVersion e definisci la strategia di migrazione:

@override
MigrationStrategy get migration => MigrationStrategy(
  onUpgrade: (migrator, from, to) async {
    if (from < 2) {
      await migrator.addColumn(todos, todos.priority);
    }
  },
);

Conclusione

Drift unisce la potenza di SQLite alla sicurezza dei tipi di Dart e a un modello reattivo che si integra naturalmente con l'architettura di Flutter. È la soluzione ideale quando hai bisogno di query complesse, relazioni e persistenza affidabile, mantenendo al tempo stesso un codice pulito e verificato a compile-time. Se la tua app cresce oltre le semplici preferenze, Drift è un investimento che ripaga.