Floor — ORM (Object-Relational Mapping) за Flutter, предоставящ типизиран слой върху SQLite. За разлика от суровите SQLite заявки, Floor генерира DAO класове от анотирани Dart модели. Според данни на Pub.dev, 2024, Floor се използва в над 3500 Flutter проекта и е сред трите най-популярни ORM решения за локално съхранение на данни, заедно с drift и hive.
Основни положения
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 се състои от три слоя: Entity (модел на таблица), DAO (интерфейс за заявки) и Database (входна точка). Генераторът създава реализации _$_Entity за картографиране на полета и _$_Dao за изпълнение на SQL. При промяна на Entity или DAO е достатъчно да рестартирате build_runner — кодът ще се актуализира автоматично. За миграция между версии на схемата, Floor използва последователни номера на версии, което гарантира целостта на данните при актуализиране на приложението на устройствата на потребителите.
Floor използва SQLite чрез пакета sqflite за платформени компилации и sqlite3 за десктоп и уеб. При стартиране на приложението, Floor създава или отваря SQLite файла, прилага миграции и подготвя DAO методи за изпълнение на заявки. Всички операции се извършват в асинхронен режим чрез Future и Stream.
Генерирането на код в Floor работи по следния принцип: парсерът чете анотациите от изходния код, създава AST (Abstract Syntax Tree) на моделите и заявките, след което генерира Dart файлове с префикс _$. Генерираният код включва картографирания ResultSet → Entity и обратно.
Floor работи с SQLite в един изолат. Всички заявки се изпълняват асинхронно, но конкурентните записи се блокират на ниво SQLite. За транзакции се използва анотация @transaction, която гарантира атомарност на групата заявки и връщане назад при грешка.
И Floor, и Drift — са ORM върху SQLite, но се различават по философия. Floor е по-близо до Room от Android, Drift — е по-реактивен с вграден Stream API и компилация на заявки чрез SQL файлове. Изборът между тях зависи от опита на екипа и необходимата реактивност.
| Характеристика | Floor | Drift |
|---|---|---|
| Тип заявки | SQL низове в @Query | Dart методи + sql файлове |
| Генериране на код | floor_generator (build_runner) | drift_dev (build_runner) |
| Реактивност | Stream от DAO | Вграден Stream API + автоматично обновяване |
| Сложност | Ниска (познат SQL) | Средна (собствен DSL) |
| Миграции | Ръчни SQL скриптове | Автоматични + ръчни |
| Съвместимост | Android, iOS, macOS | Android, iOS, Web, macOS, Linux |
Floor — избор на екипи, които вече познават SQL и Android Room. Ако разработчиците са свикнали да пишат SQL заявки ръчно и искат минимална обвивка над SQLite — Floor предоставя типизация без изучаване на нов DSL. Също така е по-лесен за дебъгване, тъй като генерираният код е четим и предвидим.
Drift предоставя по-силна реактивност и поддържа повече платформи. Ако приложението активно използва Stream за обновяване на UI, изисква сложни заявки с JOIN и подзаявки или се компилира за уеб — Drift е за предпочитане. Въпреки това, неговият праг за навлизане е по-висок поради необходимостта от изучаване на собствен DSL.
Floor е изграден около анотации. По-долу е даден пълен пример за Entity, DAO и Database за приложение за списък със задачи. След стартиране на build_runner, генерираните класове са готови за използване.
Класът TaskEntity с анотация @Entity се картографира към таблицата task. Полето с @primaryKey става първичен ключ. Интерфейсът TaskDao съдържа методи за операции с таблицата — всеки метод е анотиран с @Query, @Insert, @Update или @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);
}
Абстрактният клас с анотация @Database свързва Entity и DAO. Методът databaseBuilder създава инстанция на базата данни. След извикване на build, базата е готова: Floor отваря SQLite файла, прилага миграции и връща DAO за работа.
@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();
Floor поддържа връщане на Stream от DAO методи. При всяка промяна в таблицата, Stream излъчва нов списък. Това се интегрира с StreamBuilder във Flutter — UI се актуализира автоматично при добавяне, промяна или изтриване на записи.
@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 поддържа версиониране на базата данни чрез параметъра version в анотацията @Database. При промяна на Entity (добавяне или премахване на полета) трябва да увеличите версията и да добавите миграция. Миграцията е Dart функция, която получава транзакция и изпълнява SQL заявки ALTER TABLE.
Да предположим, че във версия 2 добавихме полето dueDate в TaskEntity. Миграцията се изпълнява с SQL заявка ALTER TABLE. Ако миграцията не е посочена, Floor извиква MigrationStrategy, където може да се зададе fallback (например повторно създаване на таблица със загуба на данни).
Floor не предоставя вградена mock рамка, но базата данни може лесно да бъде заменена в тестове. Създайте inMemoryDatabaseBuilder — той създава SQLite база данни в паметта, идентична по схема с продукционната. След всеки тест, изчистете данните чрез deleteDatabase за изолация на тестови сценарии.
Floor поддържа транзакции чрез анотация @transaction върху DAO метод. Вътре в транзакцията, няколко заявки се изпълняват последователно с гаранция за връщане назад при грешка. Пакетно вмъкване чрез @Insert с параметър List<T> оптимизира вмъкването на множество записи в едно извикване — това е няколко пъти по-бързо от вмъкване по един запис в цикъл. За масови операции използвайте пакетно вмъкване на 100–200 записа: това е оптималният баланс между скорост на изпълнение и консумация на RAM на мобилни устройства с ограничени ресурси.
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();
Често задавани въпроси
sqflite изисква ръчно писане на SQL заявки и картографиране на ResultSet в обекти. Floor генерира този код автоматично: вие описвате Entity и DAO, а типизираните методи връщат готови Dart обекти. Floor също проверява SQL заявките по време на компилация чрез анотации.
Floor няма вградени анотации за връзки (ForeignKey, @Relation), както Room. Връзките се реализират чрез ръчни SQL JOIN заявки в @Query. За сложни релационни схеми е по-добре да разгледате Drift с неговата вградена поддръжка за релации.
Floor позволява активиране на callback callback при създаване на DatabaseBuilder — към него се предава инстанция на sqflite.Database, към която може да се прикачи логер. Алтернативно, използвайте floor_doctor за визуализация на схемата и данните в режим на разработка.
Floor използва sqflite, който не работи в уеб среда. За уеб е необходима отделна компилация с sqlite3 чрез WASM. В текущата версия, Floor официално поддържа Android, iOS и macOS. За уеб използвайте Drift с адаптер за sqlite3.
Floor няма вградено кеширане — всяка заявка се изпълнява към SQLite. За кеширане на повтарящи се заявки, използвайте слой Repository с кеш в паметта (например dart_cache). Floor само генерира код за работа с SQLite, без да добавя наслагвания върху него.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също