Floor — co to jest, ORM nad SQLite we Flutter

Autor: IT Sectr Opublikowano: 2026-03-13 Czas czytania: 9 min

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 — ORM nad SQLite z kodogeneracją DAO i Entity przez adnotacje
  • Bezpieczeństwo typów — zapytania sprawdzane na etapie kompilacji, eliminujące błędy SQL w czasie wykonania
  • Wzorzec DAO — Data Access Object hermetyzuje zapytania SQL w metodach Dart
  • Migracje — wbudowane wsparcie dla wersjonowania schematu SQLite
  • Reaktywność — zapytania Flow przez Stream do automatycznego aktualizowania UI

Czym jest Floor?

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.

Architektura Floor

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.

Jak działa Floor we Flutter?

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.

Bezpieczeństwo wątków

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.

Floor vs Drift: porównanie ORM dla Flutter

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.

CharakterystykaFloorDrift
Typ zapytańCiągi SQL w @QueryMetody Dart + pliki sql
Kodogeneracjafloor_generator (build_runner)drift_dev (build_runner)
ReaktywnośćStream z DAOWbudowany Stream API + auto-updating
ZłożonośćNiska (znajomy SQL)Średnia (własny DSL)
MigracjeRęczne skrypty SQLAutomatyczne + ręczne
KompatybilnośćAndroid, iOS, macOSAndroid, iOS, Web, macOS, Linux

Kiedy wybrać Floor

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.

Kiedy lepszy Drift

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.

Przykłady kodu z Floor

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.

Definicja Entity i DAO

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.

dart
@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);
}

Inicjalizacja bazy danych

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.

dart
@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();

Reaktywne zapytania przez Stream

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.

dart
@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]),
        );
    },
)

Migracje i wersjonowanie w Floor

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.

Przykład migracji

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).

Testowanie zapytań Floor

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.

Transakcje i operacje wsadowe w Floor

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 optymalizuje wstawianie wielu rekordów w jednym wywołaniu — to kilkakrotnie szybsze niż wstawianie pojedynczych rekordów w pętli. Do operacji masowych używaj wsadowego wstawiania po 100–200 rekordów: to optymalny balans między szybkością wykonania a zużyciem pamięci RAM na urządzeniach mobilnych z ograniczonymi zasobami.

dart
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

Czym Floor różni się od surowego sqflite?

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.

Czy Floor obsługuje relacje między tabelami?

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.

Jak debugować zapytania SQL Floor?

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.

Czy można używać Floor dla kompilacji web?

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.

Jak działa buforowanie zapytań w Floor?

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

  • Floor — ORM nad SQLite z kodogeneracją Entity, DAO i Database przez adnotacje
  • Bezpieczeństwo typów — zapytania SQL sprawdzane na etapie kompilacji przez adnotację @Query
  • Wzorzec DAO — zapytania SQL hermetyzowane w metodach Dart, oddzielając model od logiki
  • Migracje — wersjonowanie schematu przez Migration z ręcznymi ALTER TABLE
  • Reaktywność — Stream z DAO do automatycznej aktualizacji UI przy zmianach
  • Ograniczenia — brak wbudowanych relacji, nie obsługuje kompilacji web
  • Zalecenie — wybieraj Floor dla projektów Flutter, gdzie zespół zna SQL i podejście Room

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.

Omów projekt

Przeczytaj również