Floor — ORM (Object-Relational Mapping) per Flutter, che fornisce un livello tipizzato su SQLite. A differenza delle query SQLite grezze, Floor genera classi DAO da modelli Dart annotati. Secondo Pub.dev, 2024, Floor è utilizzato in oltre 3500 progetti Flutter ed è tra le tre soluzioni ORM più popolari per l'archiviazione locale dei dati insieme a drift e hive.
Punti Chiave
Floor è una libreria ORM per Flutter e Dart costruita su SQLite. Utilizza annotazioni per descrivere entità (Entity), Data Access Objects (DAO) e il database (Database). La generazione del codice viene eseguita tramite build_runner e floor_generator — il compilatore crea implementazioni DAO e una classe di gestione del database. A differenza di sqflite grezzo, Floor elimina completamente la necessità di conversione manuale da ResultSet a oggetti Dart mappando automaticamente le colonne ai campi Entity tramite reflection dei tipi.
Floor segue il pattern Repository + DAO familiare agli sviluppatori Android grazie a Room. Ogni tabella è rappresentata da una classe Dart con l'annotazione @Entity, le query SQL sono raggruppate in interfacce con l'annotazione @dao e il database viene assemblato in una classe astratta con @Database. Questo approccio separa rigorosamente il modello dei dati dalla logica delle query.
Secondo Flutter Pulse (2023), Floor viene scelto nel 28% dei progetti Flutter che richiedono un database locale. I motivi principali della scelta sono la familiarità con SQL (nessuna necessità di imparare un nuovo linguaggio di query) e la verifica delle query in fase di compilazione. Inoltre, Floor genera codice leggibile che è facile da debuggare rispetto a ORM più astratti con DSL personalizzati, abbassando la barriera d'ingresso per i nuovi sviluppatori del team.
Floor è composto da tre livelli: Entity (modello di tabella), DAO (interfaccia query) e Database (punto d'ingresso). Il generatore crea implementazioni _$_Entity per il mapping dei campi e _$_Dao per l'esecuzione SQL. Quando Entity o DAO cambiano, è sufficiente riavviare build_runner — il codice viene aggiornato automaticamente. Per la migrazione tra versioni dello schema, Floor utilizza numeri di versione sequenziali, garantendo l'integrità dei dati durante l'aggiornamento dell'app sui dispositivi degli utenti.
Floor utilizza SQLite tramite il pacchetto sqflite per le build native e sqlite3 per desktop e web. All'avvio dell'app, Floor crea o apre il file SQLite, applica le migrazioni e prepara i metodi DAO per eseguire le query. Tutte le operazioni vengono eseguite in modo asincrono tramite Future e Stream.
La generazione del codice in Floor funziona come segue: il parser legge le annotazioni dai file sorgente, crea un AST (Albero Sintattico Astratto) di modelli e query, quindi genera file Dart con il prefisso _$. Il codice generato include mapper ResultSet → Entity e viceversa.
Floor lavora con SQLite in un unico isolate. Tutte le query vengono eseguite in modo asincrono, ma le scritture concorrenti vengono bloccate a livello SQLite. Per le transazioni, viene utilizzata l'annotazione @transaction, che garantisce l'atomicità di un gruppo di query e il rollback in caso di errore.
Sia Floor che Drift sono ORM su SQLite, ma differiscono per filosofia. Floor è più vicino a Room di Android, Drift è più reattivo con API Stream integrata e compilazione delle query tramite file SQL. La scelta tra di essi dipende dall'esperienza del team e dalla reattività richiesta.
| Caratteristica | Floor | Drift |
|---|---|---|
| Tipo di query | Stringhe SQL in @Query | Metodi Dart + file sql |
| Generazione codice | floor_generator (build_runner) | drift_dev (build_runner) |
| Reattività | Stream dal DAO | API Stream integrata + auto-aggiornamento |
| Complessità | Bassa (SQL familiare) | Media (DSL proprio) |
| Migrazioni | Script SQL manuali | Automatiche + manuali |
| Compatibilità | Android, iOS, macOS | Android, iOS, Web, macOS, Linux |
Floor è la scelta per i team già familiari con SQL e Android Room. Se gli sviluppatori sono abituati a scrivere query SQL manualmente e desiderano un wrapper minimo su SQLite — Floor offre la tipizzazione senza imparare un nuovo DSL. È anche più facile da debuggare poiché il codice generato è leggibile e prevedibile.
Drift offre una reattività più potente e supporta più piattaforme. Se l'app utilizza attivamente Stream per aggiornare l'UI, richiede query complesse con JOIN e sottodomande o ha come target il web — Drift è preferibile. Tuttavia, la sua barriera d'ingresso è più alta a causa della necessità di imparare il proprio DSL.
Floor è costruito attorno alle annotazioni. Di seguito è riportato un esempio completo di Entity, DAO e Database per un'applicazione di elenco attività. Dopo aver eseguito build_runner, le classi generate sono pronte all'uso.
La classe TaskEntity con annotazione @Entity viene mappata sulla tabella task. Un campo con @primaryKey diventa la chiave primaria. L'interfaccia TaskDao contiene metodi per le operazioni sulla tabella — ogni metodo è annotato con @Query, @Insert, @Update o @Delete.
@entity
class TaskEntity {
@PrimaryKey(autoGenerate: true)
final int id;
final String title;
final bool isCompleted;
final int priority;
TaskEntity({this.id, required this.title,
this.isCompleted = false, this.priority = 0});
}
@dao
abstract class TaskDao {
@Query('SELECT * FROM TaskEntity ORDER BY priority DESC')
Future<List<TaskEntity>> getAllTasks();
@Insert
Future<int> insertTask(TaskEntity task);
@Update
Future<void> updateTask(TaskEntity task);
@Query('SELECT * FROM TaskEntity WHERE isCompleted = :status')
Stream<List<TaskEntity>> watchTasks(bool status);
}
Una classe astratta con annotazione @Database collega Entity e DAO. Il metodo databaseBuilder crea un'istanza del database. Dopo aver chiamato build, il database è pronto: Floor apre il file SQLite, applica le migrazioni e restituisce il DAO per lavorare.
@Database(version: 1, entities: [TaskEntity])
abstract class AppDatabase extends FloorDatabase {
TaskDao get taskDao;
}
// Utilizzo
final database = await $FloorAppDatabase.databaseBuilder('app.db').build();
final taskDao = database.taskDao;
final tasks = await taskDao.getAllTasks();
Floor supporta la restituzione di Stream dai metodi DAO. A qualsiasi modifica nella tabella, lo Stream emette una nuova lista. Questo si integra con StreamBuilder in Flutter — l'UI si aggiorna automaticamente quando i record vengono aggiunti, modificati o eliminati.
@Query('SELECT * FROM TaskEntity ORDER BY priority DESC')
Stream<List<TaskEntity>> watchAllTasks();
// Nel widget Flutter
StreamBuilder<List<TaskEntity>>(
stream: taskDao.watchAllTasks(),
builder: (context, snapshot) {
final tasks = snapshot.data ?? [];
return ListView.builder(
itemCount: tasks.length,
itemBuilder: (_, i) => TaskTile(tasks[i]),
);
},
)
Floor supporta il versionamento del database tramite il parametro version nell'annotazione @Database. Quando Entity cambia (aggiunta o rimozione di campi), è necessario incrementare la versione e aggiungere una migrazione. Una migrazione è una funzione Dart che riceve una transazione ed esegue query SQL ALTER TABLE.
Supponiamo che nella versione 2 abbiamo aggiunto un campo dueDate a TaskEntity. La migrazione viene eseguita tramite una query SQL ALTER TABLE. Se non viene specificata una migrazione, Floor chiama MigrationStrategy dove è possibile impostare un fallback (ad esempio, ricreare la tabella con perdita di dati).
Floor non fornisce un framework mock integrato, ma il database può essere facilmente sostituito nei test. Creare un inMemoryDatabaseBuilder — crea un database SQLite in memoria identico nello schema alla produzione. Dopo ogni test, pulire i dati tramite deleteDatabase per isolare gli scenari di test.
Floor supporta le transazioni tramite l'annotazione @transaction sui metodi DAO. All'interno di una transazione, più query vengono eseguite sequenzialmente con garanzia di rollback in caso di errore. L'inserimento batch tramite @Insert con un parametro List
final migration1to2 = Migration(1, 2, (database) async {
await database.execute(
'ALTER TABLE TaskEntity ADD COLUMN dueDate TEXT'
);
});
final database = await $FloorAppDatabase.databaseBuilder('app.db')
.addMigrations([migration1to2])
.build();
Domande Frequenti
sqflite richiede la scrittura manuale di query SQL e il mapping di ResultSet agli oggetti. Floor genera questo codice automaticamente: si descrivono Entity e DAO, e i metodi tipizzati restituiscono oggetti Dart pronti all'uso. Floor verifica anche le query SQL in fase di compilazione tramite annotazioni.
Floor non ha annotazioni integrate per le relazioni (ForeignKey, @Relation) come Room. Le relazioni vengono implementate tramite query SQL JOIN manuali in @Query. Per schemi relazionali complessi, considerare Drift con il suo supporto integrato alle relazioni.
Floor consente di abilitare un callback durante la creazione di DatabaseBuilder — riceve un'istanza di sqflite.Database a cui è possibile collegare un logger. In alternativa, utilizzare floor_doctor per visualizzare schema e dati in modalità dev.
Floor utilizza sqflite, che non funziona in ambiente web. Per il web, è necessaria una build separata con sqlite3 tramite WASM. Nella versione attuale, Floor supporta ufficialmente Android, iOS e macOS. Per il web, utilizzare Drift con l'adattatore sqlite3.
Floor non ha una cache integrata — ogni query viene eseguita su SQLite. Per memorizzare nella cache le query ripetute, utilizzare un livello Repository con cache in memoria (ad esempio, dart_cache). Floor genera solo codice per lavorare con SQLite senza aggiungere overhead su di esso.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche