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 Objects (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 не надає вбудованого мок-фреймворку, але базу даних можна легко замінити в тестах. Створіть inMemoryDatabaseBuilder — він створює SQLite-базу в пам'яті, ідентичну за схемою продакшну. Після кожного тесту очищайте дані через deleteDatabase для ізоляції тестових сценаріїв.

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

Floor підтримує транзакції через анотацію @transaction на DAO-методі. Всередині транзакції виконуються кілька запитів послідовно з гарантією відкату при помилці. Пакетна вставка через @Insert з параметром List оптимізує вставку множини записів за один виклик — це в кілька разів швидше, ніж вставка по одному запису в циклі. Для масових операцій використовуйте пакетну вставку по 100–200 записів: це оптимальний баланс між швидкістю виконання та споживанням оперативної пам'яті на мобільних пристроях з обмеженими ресурсами.

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 з його вбудованою підтримкою відношень.

Як налагоджувати SQL-запити Floor?

Floor дозволяє включити callback callback при створенні DatabaseBuilder — в нього передається екземпляр sqflite.Database, на який можна повісити логер. Альтернативно використовуйте floor_doctor для візуалізації схеми та даних у dev-режимі.

Чи можна використовувати Floor для веб-збірки?

Floor використовує sqflite, який не працює у веб-оточенні. Для вебу потрібна окрема збірка з sqlite3 через WASM. У поточній версії Floor офіційно підтримує Android, iOS та macOS. Для вебу використовуйте Drift з sqlite3-адаптером.

Як працює кешування запитів у Floor?

Floor не має вбудованого кешування — кожен запит виконується до SQLite. Для кешування повторюваних запитів використовуйте шар Repository з in-memory кешем (наприклад, 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також