Floor — ORM (Object-Relational Mapping) dla Flutter, zapewniający typowaną warstwę nad SQLite. W przeciwieństwie do surowych zapytań SQLite, Floor generuje klasy DAO z adnotowanych modeli Dart. Według danych Pub.dev, 2024, Floor jest używany w ponad 3500 projektach Flutter i znajduje się w pierwszej trójce najpopularniejszych rozwiązań ORM do lokalnego przechowywania danych obok drift i hive.
Najważniejsze
Floor — to biblioteka ORM dla Flutter i Dart, zbudowana na bazie SQLite. Używa adnotacji do opisywania encji (Entity), Data Access Object (DAO) i bazy danych (Database). Generowanie kodu odbywa się przez build_runner i floor_generator — kompilator tworzy implementacje DAO i zarządzającą klasę bazy danych. W przeciwieństwie do surowego sqflite, Floor całkowicie eliminuje konieczność ręcznego przekształcania ResultSet na obiekty Dart, automatycznie mapując kolumny na pola Entity przez refleksję typów.
Floor stosuje wzorzec Repository + DAO, znany programistom Android z Room. Każda tabela jest reprezentowana przez klasę Dart z adnotacją @Entity, zapytania SQL są grupowane w interfejsach z adnotacją @dao, a baza danych jest składana w abstrakcyjnej klasie z @Database. Takie podejście ściśle rozdziela model danych i logikę zapytań.
Według Flutter Pulse (2023), Floor jest wybierany w 28% projektów Flutter, które wymagają lokalnej bazy danych. Główne powody wyboru — znajomość SQL (nie trzeba uczyć się nowego języka zapytań) i sprawdzanie zapytań na etapie kompilacji. Ponadto Floor generuje czytelny kod, który łatwo debugować w przeciwieństwie do bardziej abstrakcyjnych ORM z własnym DSL, co obniża próg wejścia dla nowych programistów w zespole.
Floor składa się z trzech warstw: Entity (model tabeli), DAO (interfejs zapytań) i Database (punkt wejścia). Generator tworzy implementacje _$_Entity do mapowania pól oraz _$_Dao do wykonywania SQL. Przy zmianie Entity lub DAO wystarczy ponownie uruchomić build_runner — kod zaktualizuje się automatycznie. Do migracji między wersjami schematu Floor używa sekwencyjnych numerów wersji, co gwarantuje integralność danych podczas aktualizacji aplikacji na urządzeniach użytkowników.
Floor używa SQLite przez pakiet sqflite dla kompilacji platformowych oraz sqlite3 dla desktopu i web. Przy uruchomieniu aplikacji Floor tworzy lub otwiera plik SQLite, stosuje migracje i przygotowuje metody DAO do wykonywania zapytań. Wszystkie operacje są wykonywane w trybie asynchronicznym przez Future i Stream.
Generowanie kodu w Floor działa na następującej zasadzie: parser odczytuje adnotacje z kodów źródłowych, tworzy AST (Abstract Syntax Tree) modeli i zapytań, a następnie generuje pliki Dart z prefiksem _$. Wygenerowany kod zawiera mapery ResultSet → Entity i odwrotnie.
Floor pracuje z SQLite w jednym izolacie. Wszystkie zapytania są wykonywane asynchronicznie, ale współbieżne zapisy są blokowane na poziomie SQLite. Do transakcji używana jest adnotacja @transaction, która gwarantuje atomowość grupy zapytań i wycofanie przy błędzie.
Zarówno Floor, jak i Drift — to ORM nad SQLite, ale różnią się filozofią. Floor jest bliższy Room z Androida, Drift — bardziej reaktywny z wbudowanym Stream API i kompilacją zapytań przez pliki SQL. Wybór między nimi zależy od doświadczenia zespołu i wymaganej reaktywności.
| Charakterystyka | Floor | Drift |
|---|---|---|
| Typ zapytań | Ciągi SQL w @Query | Metody Dart + pliki sql |
| Kodogeneracja | floor_generator (build_runner) | drift_dev (build_runner) |
| Reaktywność | Stream z DAO | Wbudowany Stream API + auto-updating |
| Złożoność | Niska (znajomy SQL) | Średnia (własny DSL) |
| Migracje | Ręczne skrypty SQL | Automatyczne + ręczne |
| Kompatybilność | Android, iOS, macOS | Android, iOS, Web, macOS, Linux |
Floor — wybór zespołów znających już SQL i Android Room. Jeśli programiści przywykli do ręcznego pisania zapytań SQL i chcą minimalną nakładkę nad SQLite — Floor zapewnia typowanie bez nauki nowego DSL. Jest również łatwiejszy w debugowaniu, ponieważ wygenerowany kod jest czytelny i przewidywalny.
Drift zapewnia potężniejszą reaktywność i obsługuje więcej platform. Jeśli aplikacja aktywnie używa Stream do aktualizacji UI, wymaga złożonych zapytań z JOIN i podzapytaniami lub działa na web — Drift jest preferowany. Jednak jego próg wejścia jest wyższy ze względu na konieczność nauki własnego DSL.
Floor opiera się na adnotacjach. Poniżej znajduje się pełny przykład Entity, DAO i Database dla aplikacji listy zadań. Po uruchomieniu build_runner wygenerowane klasy są gotowe do użycia.
Klasa TaskEntity z adnotacją @Entity mapuje się na tabelę task. Pole z @primaryKey staje się kluczem głównym. Interfejs TaskDao zawiera metody do operacji na tabeli — każda metoda jest adnotowana @Query, @Insert, @Update lub @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);
}
Abstrakcyjna klasa z adnotacją @Database łączy Entity i DAO. Metoda databaseBuilder tworzy instancję bazy. Po wywołaniu build baza jest gotowa: Floor otwiera plik SQLite, stosuje migracje i zwraca DAO do pracy.
@Database(version: 1, entities: [TaskEntity])
abstract class AppDatabase extends FloorDatabase {
TaskDao get taskDao;
}
// Użycie
final database = await $FloorAppDatabase.databaseBuilder('app.db').build();
final taskDao = database.taskDao;
final tasks = await taskDao.getAllTasks();
Floor obsługuje zwracanie Stream z metod DAO. Przy każdej zmianie w tabeli Stream emituje nową listę. To integruje się z StreamBuilder we Flutter — UI aktualizuje się automatycznie przy dodawaniu, modyfikacji lub usuwaniu rekordów.
@Query('SELECT * FROM TaskEntity ORDER BY priority DESC')
Stream<List<TaskEntity>> watchAllTasks();
// W widżecie 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 obsługuje wersjonowanie bazy danych przez parametr version w adnotacji @Database. Przy zmianie Entity (dodaniu lub usunięciu pól) należy zwiększyć wersję i dodać migrację. Migracja to funkcja Dart, która otrzymuje transakcję i wykonuje zapytania SQL ALTER TABLE.
Zakładając, że w wersji 2 dodaliśmy pole dueDate w TaskEntity. Migracja jest wykonywana zapytaniem SQL ALTER TABLE. Jeśli migracja nie jest określona, Floor wywołuje MigrationStrategy, gdzie można ustawić fallback (np. odtworzenie tabeli z utratą danych).
Floor nie zapewnia wbudowanego frameworka do mockowania, ale bazę danych można łatwo zastąpić w testach. Utwórz inMemoryDatabaseBuilder — tworzy bazę SQLite w pamięci, identyczną schematem z produkcyjną. Po każdym teście czyść dane przez deleteDatabase dla izolacji scenariuszy testowych.
Floor obsługuje transakcje przez adnotację @transaction na metodzie DAO. Wewnątrz transakcji wykonywanych jest kilka zapytań sekwencyjnie z gwarancją wycofania przy błędzie. Wstawianie wsadowe przez @Insert z parametrem 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();
Często zadawane pytania
sqflite wymaga ręcznego pisania zapytań SQL i mapowania ResultSet na obiekty. Floor generuje ten kod automatycznie: opisujesz Entity i DAO, a typowane metody zwracają gotowe obiekty Dart. Floor również sprawdza zapytania SQL na etapie kompilacji przez adnotacje.
Floor nie ma wbudowanych adnotacji dla relacji (ForeignKey, @Relation), jak Room. Relacje są implementowane przez ręczne zapytania SQL JOIN w @Query. Dla złożonych schematów relacyjnych lepiej rozważyć Drift z jego wbudowanym wsparciem dla relacji.
Floor pozwala włączyć callback callback przy tworzeniu DatabaseBuilder — do niego przekazywana jest instancja sqflite.Database, na którą można podpiąć logger. Alternatywnie użyj floor_doctor do wizualizacji schematu i danych w trybie deweloperskim.
Floor używa sqflite, który nie działa w środowisku web. Dla web potrzebna będzie osobna kompilacja z sqlite3 przez WASM. W obecnej wersji Floor oficjalnie obsługuje Android, iOS i macOS. Dla web używaj Drift z adapterem sqlite3.
Floor nie ma wbudowanego buforowania — każde zapytanie jest wykonywane do SQLite. Do buforowania powtarzających się zapytań używaj warstwy Repository z pamięcią podręczną in-memory (np. dart_cache). Floor jedynie generuje kod do pracy z SQLite, nie dodając nakładek nad nim.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również