Drift (dříve Moor) — reaktivní ORM pro Flutter a Dart, postavené nad SQLite s vlastním DSL pro dotazy. Na rozdíl od tradičních ORM Drift kompiluje Dart dotazy do SQL během fáze sestavení, čímž eliminuje runtime chyby. Podle Drift Docs, 2024 generuje Drift až o 40 % více kódu než ruční SQL dotazy, ale zcela eliminuje ruční psaní SQL a nahrazuje ho typově bezpečnou Dart syntaxí.
Hlavní body
Drift — ORM pro Dart a Flutter, dříve známé jako Moor. Vyvinul ho Simon Binder v roce 2019 a od té doby prošlo několika major verzemi. Drift kompiluje Dart dotazy do SQL v době sestavení pomocí drift_dev a build_runner, což poskytuje úplnou typovou bezpečnost a eliminuje syntaktické chyby SQL za běhu.
Na rozdíl od Floor používá Drift vlastní DSL (Domain-Specific Language) pro vytváření dotazů — vývojář píše v Dartu a generátor to překládá do SQL. To umožňuje IDE kontrolovat syntaxi, automaticky doplňovat pole tabulek a refaktorovat datový model bez strachu z rozbití dotazu.
Podle Drift (2024) je knihovna používána ve více než 8000 Flutter projektech. Podporuje všechny populární platformy: Android přes sqflite, iOS přes sqflite, web přes sqlite3 WASM, desktop přes nativní ovladač sqlite3.
Moor byl přejmenován na Drift ve verzi 2.0 (2022). Důvodem byl konflikt názvů s jinými projekty a touha distancovat se od starého kódu. API zůstalo kompatibilní: pro migraci stačí změnit import z moor na drift a aktualizovat závislosti.
Drift poskytuje: vestavěné Stream dotazy s automatickou aktualizací při změně dat, podporu transakcí s vrácením zpět, vlastní SQL dotazy přes rawQuery, DAO vzor pro zapouzdření logiky, multiplatformní migrace a integraci s Riverpod a BLoC přes balíčky drift_riverpod a drift_bloc.
Drift používá generování kódu v době kompilace. Vývojář popisuje tabulky pomocí @DataClass anotací nebo Dart tříd rozšiřujících Table. Generátor vytváří pomocné třídy: Companion (pro nullable pole při vkládání/aktualizaci), DriftDatabase (vstupní bod) a implementace DAO.
SQLite dotazy nejsou Driftem prováděny přímo. Místo toho vývojář píše v Dartu: select(tasks).where(tasks.priority.greaterThan(3)).build(). Generátor to překládá do SQL a za běhu Drift jednoduše odešle hotový SQL dotaz do SQLite. To kombinuje pohodlí Dart syntaxe s výkonem nativního SQL.
Drift podporuje dva režimy dotazů: DSL (doporučený) a raw SQL. DSL dotazy jsou bezpečnější — kompilátor kontroluje názvy polí, typy a kompatibilitu. Raw SQL je potřeba pro složité dotazy nepokryté DSL: okenní funkce, rekurzivní CTE, specifická SQLite rozšíření.
Drift nabízí dva způsoby psaní dotazů: Dart DSL (nativní) a raw SQL (pro složité případy). DSL je preferovaný pro 90 % scénářů: je bezpečnější, čitelnější a podporuje refaktorování. Raw SQL se používá pouze když DSL nepokrývá požadovanou konstrukci.
| Aspekt | Drift DSL | Raw SQL v Drift |
|---|---|---|
| Typová bezpečnost | Plná (kompilace) | Ne (runtime) |
| Automatické doplňování | Ano (IDE) | Pouze v .sql souborech |
| Refaktorování | Automatické | Ruční vyhledávání v řetězcích |
| Složité JOIN | Podporované | Plná svoboda |
| Okenní funkce | Omezené | Plná podpora |
| Reaktivita | Vestavěná (Stream) | Přes .watch() |
Drift DSL — hlavní způsob práce. Pokrývá SELECT, INSERT, UPDATE, DELETE, WHERE, ORDER BY, LIMIT, JOIN a seskupování. Pro všechny typické CRUD dotazy použijte DSL: je kratší, bezpečnější a automaticky aktualizuje Stream při změnách.
Raw SQL v Drift je potřeba pro: vlastní SQLite funkce (FTS5, JSON1), složité poddotazy s EXISTS, INSERT OR REPLACE, hromadné UPDATE s CASE a pro dotazy kde je výkon kritický a DSL negeneruje optimální plán provádění. Raw SQL lze psát v .sql souborech s podporou typizace přes drift_dev.
Drift používá třídy rozšiřující Table nebo anotaci @DataClass. Níže je kompletní příklad modelu Task s dotazy přes DSL, raw SQL a reaktivní aktualizací. Po spuštění build_runner jsou všechny vygenerované třídy připraveny.
Třída Tasks rozšiřuje Table a definuje sloupce. Každý sloupec je výraz typu Column<T>. Parametry: withDefault() nastavuje výchozí hodnotu, autoIncrement() — autoinkrement. Databáze je abstraktní třída rozšiřující $DriftDatabase.
class Tasks extends Table {
IntColumn get id => integer().autoIncrement();
TextColumn get title => text().withDefault(const Constant(''))();
BoolColumn get isCompleted => boolean().withDefault(const Constant(false))();
IntColumn get priority => integer().withDefault(const Constant(0))();
}
@DriftDatabase(tables: [Tasks])
class AppDatabase extends $AppDatabase {
AppDatabase(QueryExecutor e) : super(e);
}
Drift generuje metody into(tasks).insert(), select(tasks), update(tasks) a delete(tasks) pro tabulky. Všechny operace vracejí Future — práce s SQLite je asynchronní. Pro sledování změn použijte .watch() místo .get().
// Vložit
await into(tasks).insert(TasksCompanion.insert(
title: Value('Koupit potraviny'),
priority: Value(3),
));
// Čtení s filtrem
final highPriority = await (select(tasks)
..where((t) => t.priority.greaterThan(2))
..orderBy([(t) => OrderingTerm(expression: t.priority, mode: OrderingMode.desc)]))
.get();
// Reaktivní pozorování
select(tasks).watch().listen((tasksList) {
// tasksList — List, aktualizuje se při každé změně tabulky
updateUi(tasksList);
});
Pro složité dotazy Drift umožňuje psát raw SQL při zachování typizace. Metoda customSelect přijímá řetězec dotazu a vrací typovaný výsledek přes generátor kódu. Tento přístup kombinuje flexibilitu SQL s typovou bezpečností Drift.
final result = await customSelect(
'SELECT title, COUNT(*) as cnt FROM tasks GROUP BY title',
readsFrom: { tasks },
).get();
for (final row in result) {
print('${row.readString("title")}: ${row.readInt("cnt")}');
}
Drift podporuje jak automatické migrace (pro jednoduché změny), tak ruční (pro složité transformace). Verze databáze se nastavuje v konstruktoru AppDatabase. Při neshodě verze Drift aplikuje všechny nevyřízené migrace postupně.
Pro přidání sloupce s výchozí hodnotou může Drift vygenerovat migraci automaticky přes MigrationStrategy. Pokud změna nenarušuje existující data (přidání nullable pole), lze použít beforeOpen s kontrolou verze a provedením ALTER TABLE.
Pro složité změny (přejmenování tabulky, sloučení dat, změna typu sloupce) Drift vyžaduje ruční SQL migraci. Migrace se nastavují přes parametr migrations ve třídě databáze. Každá migrace je objekt s čísly from/to a SQL dotazy.
Drift podporuje spuštění v testovacím režimu přes NativeDatabase.memory(). Databáze v paměti je vytvořena od začátku před každým testem a zničena po něm. Pro mockování použijte balíček mocktail s zamockovaným QueryExecutor. Drift také poskytuje DatabaseTestHelper pro integrační testy s kontrolou migrací a dotazů.
Drift podporuje DAO (Data Access Object) přes abstraktní třídy s anotací @DriftAccessor. DAO zapouzdřuje dotazy na jednu nebo více tabulek a lze jej testovat nezávisle na databázi. Na rozdíl od přímých dotazů přes Database umožňuje DAO znovupoužití logiky dotazů mezi různými částmi aplikace a zjednodušuje unit testování.
@DriftDatabase(tables: [Tasks])
class AppDatabase extends $AppDatabase {
AppDatabase(QueryExecutor e) : super(e) {
migrations.add(Migration(1, 2, (m) async {
await m.addColumn(tasks, tasks.dueDate);
await m.createIndex(tasks.idxPriority);
}));
}
}
Často kladené otázky
Drift používá vlastní DSL místo SQL řetězců, což poskytuje plnou typovou bezpečnost a automatické doplňování v IDE. Floor používá SQL řetězce v anotaci @Query. Drift také podporuje více platforem (včetně webu) a má vestavěnou reaktivitu přes Stream, zatímco v Floor je třeba Stream deklarovat ručně.
Ano, Drift podporuje migrace se zachováním dat. Pro přidání sloupců použijte addColumn v Migration. Pro složité transformace (přejmenování, sloučení) pište raw SQL uvnitř migrace. Pokud migrace není zadána, Drift znovu vytvoří databázi se ztrátou dat při neshodě schématu.
Drift vyžaduje generování kódu přes build_runner a drift_dev. Bez generování nelze vytvořit typované dotazy. Pro malé projekty však Drift podporuje sqlparser — ruční psaní SQL souborů s automatickou typizací, ale stále to vyžaduje fázi generování.
Pro integraci s Riverpod použijte balíček drift_riverpod. Poskytuje providery pro Database, DAO a Stream dotazy. Příklad: final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — UI se automaticky přestaví při změně dat.
Drift nemá vestavěné šifrování, ale podporuje připojení vlastních sqlite3 knihoven s SEE (SQLite Encryption Extension). Pro mobilní platformy použijte sqflite_sqlcipher jako QueryExecutor — Drift funguje s jakoukoli implementací SQLite přes abstraktní QueryExecutor.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také