Floor — какво е това, ORM върху SQLite във Flutter

Автор: IT Sectr Публикувано: 2026-03-13 Време за четене: 9 мин

Floor — ORM (Object-Relational Mapping) за Flutter, предоставящ типизиран слой върху SQLite. За разлика от суровите SQLite заявки, Floor генерира DAO класове от анотирани Dart модели. Според данни на Pub.dev, 2024, Floor се използва в над 3500 Flutter проекта и е сред трите най-популярни ORM решения за локално съхранение на данни, заедно с drift и hive.

Основни положения

  • Floor — ORM върху SQLite с генериране на код за DAO и Entity чрез анотации
  • Типова безопасност — заявките се проверяват по време на компилация, елиминирайки runtime грешки в SQL
  • DAO модел — Data Access Object капсулира SQL заявки в Dart методи
  • Миграции — вградена поддръжка за версиониране на SQLite схема
  • Реактивност — Flow заявки чрез Stream за автоматично обновяване на UI

Какво е Floor?

Floor — е ORM библиотека за Flutter и Dart, изградена върху SQLite. Тя използва анотации за описване на обекти (Entity), Data Access Object (DAO) и база данни (Database). Генерирането на код се извършва чрез build_runner и floor_generator — компилаторът създава реализации на DAO и управляващия клас на базата данни. За разлика от суровия sqflite, Floor напълно елиминира необходимостта от ръчно преобразуване на ResultSet в Dart обекти, автоматично картографирайки колони към полета на Entity чрез отражение на типове.

Floor следва модела Repository + DAO, познат на Android разработчиците от Room. Всяка таблица е представена от Dart клас с анотация @Entity, SQL заявките са групирани в интерфейси с анотация @dao, а базата данни е съставена в абстрактен клас с @Database. Този подход стриктно разделя модела на данни и логиката на заявките.

Според Flutter Pulse (2023), Floor се избира в 28% от Flutter проектите, които изискват локална база данни. Основните причини за избора — познаване на SQL (не е нужно да учите нов език за заявки) и проверка на заявките по време на компилация. Освен това, Floor генерира четим код, който лесно се дебъгва за разлика от по-абстрактните ORM с персонализиран DSL, което намалява прага за навлизане за нови разработчици в екипа.

Архитектура на Floor

Floor се състои от три слоя: Entity (модел на таблица), DAO (интерфейс за заявки) и Database (входна точка). Генераторът създава реализации _$_Entity за картографиране на полета и _$_Dao за изпълнение на SQL. При промяна на Entity или DAO е достатъчно да рестартирате build_runner — кодът ще се актуализира автоматично. За миграция между версии на схемата, Floor използва последователни номера на версии, което гарантира целостта на данните при актуализиране на приложението на устройствата на потребителите.

Как работи Floor във Flutter?

Floor използва SQLite чрез пакета sqflite за платформени компилации и sqlite3 за десктоп и уеб. При стартиране на приложението, Floor създава или отваря SQLite файла, прилага миграции и подготвя DAO методи за изпълнение на заявки. Всички операции се извършват в асинхронен режим чрез Future и Stream.

Генерирането на код в Floor работи по следния принцип: парсерът чете анотациите от изходния код, създава AST (Abstract Syntax Tree) на моделите и заявките, след което генерира Dart файлове с префикс _$. Генерираният код включва картографирания ResultSet → Entity и обратно.

Безопасност на нишките

Floor работи с SQLite в един изолат. Всички заявки се изпълняват асинхронно, но конкурентните записи се блокират на ниво SQLite. За транзакции се използва анотация @transaction, която гарантира атомарност на групата заявки и връщане назад при грешка.

Floor vs Drift: сравнение на ORM за Flutter

И Floor, и Drift — са ORM върху SQLite, но се различават по философия. Floor е по-близо до Room от Android, Drift — е по-реактивен с вграден Stream API и компилация на заявки чрез SQL файлове. Изборът между тях зависи от опита на екипа и необходимата реактивност.

ХарактеристикаFloorDrift
Тип заявкиSQL низове в @QueryDart методи + sql файлове
Генериране на кодfloor_generator (build_runner)drift_dev (build_runner)
РеактивностStream от DAOВграден Stream API + автоматично обновяване
СложностНиска (познат SQL)Средна (собствен DSL)
МиграцииРъчни SQL скриптовеАвтоматични + ръчни
СъвместимостAndroid, iOS, macOSAndroid, iOS, Web, macOS, Linux

Кога да изберете Floor

Floor — избор на екипи, които вече познават SQL и Android Room. Ако разработчиците са свикнали да пишат SQL заявки ръчно и искат минимална обвивка над SQLite — Floor предоставя типизация без изучаване на нов DSL. Също така е по-лесен за дебъгване, тъй като генерираният код е четим и предвидим.

Кога Drift е по-добър

Drift предоставя по-силна реактивност и поддържа повече платформи. Ако приложението активно използва Stream за обновяване на UI, изисква сложни заявки с JOIN и подзаявки или се компилира за уеб — Drift е за предпочитане. Въпреки това, неговият праг за навлизане е по-висок поради необходимостта от изучаване на собствен DSL.

Примери за код с Floor

Floor е изграден около анотации. По-долу е даден пълен пример за Entity, DAO и Database за приложение за списък със задачи. След стартиране на build_runner, генерираните класове са готови за използване.

Дефиниция на Entity и DAO

Класът TaskEntity с анотация @Entity се картографира към таблицата task. Полето с @primaryKey става първичен ключ. Интерфейсът TaskDao съдържа методи за операции с таблицата — всеки метод е анотиран с @Query, @Insert, @Update или @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);
}

Инициализация на база данни

Абстрактният клас с анотация @Database свързва Entity и DAO. Методът databaseBuilder създава инстанция на базата данни. След извикване на build, базата е готова: Floor отваря SQLite файла, прилага миграции и връща DAO за работа.

dart
@Database(version: 1, entities: [TaskEntity])
abstract class AppDatabase extends FloorDatabase {
    TaskDao get taskDao;
}

// Използване
final database = await $FloorAppDatabase.databaseBuilder('app.db').build();
final taskDao = database.taskDao;
final tasks = await taskDao.getAllTasks();

Реактивни заявки чрез Stream

Floor поддържа връщане на Stream от DAO методи. При всяка промяна в таблицата, Stream излъчва нов списък. Това се интегрира с StreamBuilder във Flutter — UI се актуализира автоматично при добавяне, промяна или изтриване на записи.

dart
@Query('SELECT * FROM TaskEntity ORDER BY priority DESC')
Stream<List<TaskEntity>> watchAllTasks();

// Във 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

Floor поддържа версиониране на базата данни чрез параметъра version в анотацията @Database. При промяна на Entity (добавяне или премахване на полета) трябва да увеличите версията и да добавите миграция. Миграцията е Dart функция, която получава транзакция и изпълнява SQL заявки ALTER TABLE.

Пример за миграция

Да предположим, че във версия 2 добавихме полето dueDate в TaskEntity. Миграцията се изпълнява с SQL заявка ALTER TABLE. Ако миграцията не е посочена, Floor извиква MigrationStrategy, където може да се зададе fallback (например повторно създаване на таблица със загуба на данни).

Тестване на Floor заявки

Floor не предоставя вградена mock рамка, но базата данни може лесно да бъде заменена в тестове. Създайте inMemoryDatabaseBuilder — той създава SQLite база данни в паметта, идентична по схема с продукционната. След всеки тест, изчистете данните чрез deleteDatabase за изолация на тестови сценарии.

Транзакции и пакетни операции в Floor

Floor поддържа транзакции чрез анотация @transaction върху DAO метод. Вътре в транзакцията, няколко заявки се изпълняват последователно с гаранция за връщане назад при грешка. Пакетно вмъкване чрез @Insert с параметър List<T> оптимизира вмъкването на множество записи в едно извикване — това е няколко пъти по-бързо от вмъкване по един запис в цикъл. За масови операции използвайте пакетно вмъкване на 100–200 записа: това е оптималният баланс между скорост на изпълнение и консумация на RAM на мобилни устройства с ограничени ресурси.

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

Често задавани въпроси

С какво Floor се различава от суровия sqflite?

sqflite изисква ръчно писане на SQL заявки и картографиране на ResultSet в обекти. Floor генерира този код автоматично: вие описвате Entity и DAO, а типизираните методи връщат готови Dart обекти. Floor също проверява SQL заявките по време на компилация чрез анотации.

Поддържа ли Floor връзки между таблици?

Floor няма вградени анотации за връзки (ForeignKey, @Relation), както Room. Връзките се реализират чрез ръчни SQL JOIN заявки в @Query. За сложни релационни схеми е по-добре да разгледате Drift с неговата вградена поддръжка за релации.

Как да дебъгвам Floor SQL заявки?

Floor позволява активиране на callback callback при създаване на DatabaseBuilder — към него се предава инстанция на sqflite.Database, към която може да се прикачи логер. Алтернативно, използвайте floor_doctor за визуализация на схемата и данните в режим на разработка.

Може ли Floor да се използва за уеб компилация?

Floor използва sqflite, който не работи в уеб среда. За уеб е необходима отделна компилация с sqlite3 чрез WASM. В текущата версия, Floor официално поддържа Android, iOS и macOS. За уеб използвайте Drift с адаптер за sqlite3.

Как работи кеширането на заявки в Floor?

Floor няма вградено кеширане — всяка заявка се изпълнява към SQLite. За кеширане на повтарящи се заявки, използвайте слой Repository с кеш в паметта (например dart_cache). Floor само генерира код за работа с SQLite, без да добавя наслагвания върху него.

Резюме

  • Floor — ORM върху SQLite с генериране на код за Entity, DAO и Database чрез анотации
  • Типова безопасност — SQL заявките се проверяват по време на компилация чрез анотация @Query
  • DAO модел — SQL заявките са капсулирани в Dart методи, разделяйки модела и логиката
  • Миграции — версиониране на схемата чрез Migration с ръчно ALTER TABLE
  • Реактивност — Stream от DAO за автоматично обновяване на UI при промени
  • Ограничения — няма вградени връзки, не поддържа уеб компилация
  • Препоръка — изберете Floor за Flutter проекти, където екипът познава SQL и подхода на Room

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също