Floor — ORM (Object-Relational Mapping) pentru Flutter, care oferă un strat tipizat peste SQLite. Spre deosebire de interogările SQLite brute, Floor generează clase DAO din modele Dart adnotate. Conform datelor Pub.dev, 2024, Floor este utilizat în peste 3500 de proiecte Flutter și se află în top trei cele mai populare soluții ORM pentru stocarea locală a datelor, alături de drift și hive.
Principalele puncte
Floor — este o bibliotecă ORM pentru Flutter și Dart, construită pe baza SQLite. Utilizează adnotări pentru a descrie entități (Entity), Data Access Object (DAO) și baza de date (Database). Generarea codului se realizează prin build_runner și floor_generator — compilatorul creează implementări DAO și clasa de gestionare a bazei de date. Spre deosebire de sqflite brut, Floor elimină complet necesitatea conversiei manuale a ResultSet în obiecte Dart, mapând automat coloanele pe câmpurile Entity prin reflexia tipurilor.
Floor urmează modelul Repository + DAO, cunoscut dezvoltatorilor Android din Room. Fiecare tabel este reprezentat de o clasă Dart cu adnotarea @Entity, interogările SQL sunt grupate în interfețe cu adnotarea @dao, iar baza de date este compusă într-o clasă abstractă cu @Database. Această abordare separă strict modelul de date și logica interogărilor.
Conform Flutter Pulse (2023), Floor este ales în 28% din proiectele Flutter care necesită o bază de date locală. Principalele motive de alegere — cunoașterea SQL (nu este nevoie să înveți un nou limbaj de interogare) și verificarea interogărilor la compilare. În plus, Floor generează cod citibil, care este ușor de depanat spre deosebire de ORM-uri mai abstracte cu DSL personalizat, ceea ce reduce pragul de intrare pentru noii dezvoltatori din echipă.
Floor constă din trei straturi: Entity (modelul tabelului), DAO (interfața interogărilor) și Database (punctul de intrare). Generatorul creează implementări _$_Entity pentru maparea câmpurilor și _$_Dao pentru executarea SQL. La modificarea Entity sau DAO este suficient să reporniți build_runner — codul se va actualiza automat. Pentru migrarea între versiunile schemei, Floor utilizează numere de versiune secvențiale, garantând integritatea datelor la actualizarea aplicației pe dispozitivele utilizatorilor.
Floor utilizează SQLite prin pachetul sqflite pentru compilări platformă și sqlite3 pentru desktop și web. La pornirea aplicației, Floor creează sau deschide fișierul SQLite, aplică migrările și pregătește metodele DAO pentru executarea interogărilor. Toate operațiile se execută în mod asincron prin Future și Stream.
Generarea codului în Floor funcționează după următorul principiu: parserul citește adnotările din codurile sursă, creează AST (Abstract Syntax Tree) al modelelor și interogărilor, apoi generează fișiere Dart cu prefixul _$. Codul generat include mapări ResultSet → Entity și invers.
Floor lucrează cu SQLite într-un singur izolat. Toate interogările se execută asincron, dar scrierile concurente sunt blocate la nivelul SQLite. Pentru tranzacții se utilizează adnotarea @transaction, care garantează atomicitatea grupului de interogări și revenirea în caz de eroare.
Atât Floor, cât și Drift — sunt ORM peste SQLite, dar diferă prin filozofie. Floor este mai aproape de Room din Android, Drift — mai reactiv cu Stream API încorporat și compilarea interogărilor prin fișiere SQL. Alegerea dintre ele depinde de experiența echipei și reactivitatea necesară.
| Caracteristică | Floor | Drift |
|---|---|---|
| Tip interogări | Stringuri SQL în @Query | Metode Dart + fișiere sql |
| Generare cod | floor_generator (build_runner) | drift_dev (build_runner) |
| Reactitivitate | Stream din DAO | Stream API încorporat + auto-updating |
| Complexitate | Scăzută (SQL familiar) | Medie (DSL propriu) |
| Migrări | Scripturi SQL manuale | Automatice + manuale |
| Compatibilitate | Android, iOS, macOS | Android, iOS, Web, macOS, Linux |
Floor — alegerea echipelor care cunosc deja SQL și Android Room. Dacă dezvoltatorii sunt obișnuiți să scrie interogări SQL manual și doresc un strat minim peste SQLite — Floor oferă tipizare fără a învăța un nou DSL. Este, de asemenea, mai ușor de depanat, deoarece codul generat este citibil și previzibil.
Drift oferă o reactivitate mai puternică și suportă mai multe platforme. Dacă aplicația utilizează activ Stream pentru actualizarea UI, necesită interogări complexe cu JOIN și subinterogări sau este construită pentru web — Drift este preferabil. Totuși, pragul său de intrare este mai ridicat din cauza necesității de a învăța propriul DSL.
Floor este construit în jurul adnotărilor. Mai jos este un exemplu complet de Entity, DAO și Database pentru o aplicație listă de sarcini. După rularea build_runner, clasele generate sunt gata de utilizare.
Clasa TaskEntity cu adnotarea @Entity se mapează pe tabelul task. Câmpul cu @primaryKey devine cheia primară. Interfața TaskDao conține metode pentru operații cu tabelul — fiecare metodă este adnotată cu @Query, @Insert, @Update sau @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);
}
Clasa abstractă cu adnotarea @Database leagă Entity și DAO. Metoda databaseBuilder creează o instanță a bazei de date. După apelarea build, baza este gata: Floor deschide fișierul SQLite, aplică migrările și returnează DAO pentru lucru.
@Database(version: 1, entities: [TaskEntity])
abstract class AppDatabase extends FloorDatabase {
TaskDao get taskDao;
}
// Utilizare
final database = await $FloorAppDatabase.databaseBuilder('app.db').build();
final taskDao = database.taskDao;
final tasks = await taskDao.getAllTasks();
Floor suportă returnarea Stream din metodele DAO. La orice modificare în tabel, Stream emite o nouă listă. Acest lucru se integrează cu StreamBuilder în Flutter — UI se actualizează automat la adăugarea, modificarea sau ștergerea înregistrărilor.
@Query('SELECT * FROM TaskEntity ORDER BY priority DESC')
Stream<List<TaskEntity>> watchAllTasks();
// În widgetul 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 suportă versionarea bazei de date prin parametrul version în adnotarea @Database. La modificarea Entity (adăugarea sau eliminarea câmpurilor) trebuie să creșteți versiunea și să adăugați o migrare. Migrarea este o funcție Dart care primește o tranzacție și execută interogări SQL ALTER TABLE.
Să presupunem că în versiunea 2 am adăugat câmpul dueDate în TaskEntity. Migrarea se execută prin interogarea SQL ALTER TABLE. Dacă migrarea nu este specificată, Floor apelează MigrationStrategy, unde se poate seta un fallback (de exemplu, recrearea tabelului cu pierderea datelor).
Floor nu oferă un framework de mock încorporat, dar baza de date poate fi ușor înlocuită în teste. Creați inMemoryDatabaseBuilder — creează o bază SQLite în memorie, identică ca schemă cu cea de producție. După fiecare test, ștergeți datele prin deleteDatabase pentru izolarea scenariilor de test.
Floor suportă tranzacții prin adnotarea @transaction pe metoda DAO. În interiorul tranzacției, mai multe interogări se execută secvențial cu garanția revenirii la eroare. Inserarea batch prin @Insert cu parametrul List<T> optimizează inserarea mai multor înregistrări într-un singur apel — este de câteva ori mai rapidă decât inserarea individuală în buclă. Pentru operații masive, utilizați inserarea batch cu 100–200 de înregistrări: acesta este echilibrul optim între viteza de executare și consumul de RAM pe dispozitivele mobile cu resurse limitate.
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();
Întrebări frecvente
sqflite necesită scrierea manuală a interogărilor SQL și maparea ResultSet în obiecte. Floor generează acest cod automat: descrieți Entity și DAO, iar metodele tipizate returnează obiecte Dart gata preparate. Floor verifică, de asemenea, interogările SQL la compilare prin adnotări.
Floor nu are adnotări încorporate pentru relații (ForeignKey, @Relation), ca Room. Relațiile se implementează prin interogări manuale SQL JOIN în @Query. Pentru scheme relaționale complexe, este mai bine să luați în considerare Drift cu suportul său încorporat pentru relații.
Floor permite activarea callback-ului callback la crearea DatabaseBuilder — în el se transmite instanța sqflite.Database de care se poate atașa un logger. Alternativ, utilizați floor_doctor pentru vizualizarea schemei și datelor în modul de dezvoltare.
Floor utilizează sqflite, care nu funcționează în mediul web. Pentru web este necesară o compilare separată cu sqlite3 prin WASM. În versiunea actuală, Floor suportă oficial Android, iOS și macOS. Pentru web, utilizați Drift cu adaptorul sqlite3.
Floor nu are cache încorporat — fiecare interogare se execută către SQLite. Pentru cache-ul interogărilor repetitive, utilizați stratul Repository cu cache în memorie (de exemplu, dart_cache). Floor doar generează cod pentru lucrul cu SQLite, fără a adăuga straturi suplimentare.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și