Drift (tidigare Moor) — reaktiv ORM för Flutter och Dart, byggd ovanpå SQLite med eget DSL för frågor. Till skillnad från traditionella ORM kompilerar Drift Dart-frågor till SQL vid byggtid, vilket eliminerar runtime-fel. Enligt Drift Docs, 2024 genererar Drift upp till 40% mer kod än manuella SQL-frågor, men eliminerar helt manuell SQL-skrivning och ersätter den med typsäker Dart-syntax.
Huvudpunkter
Drift — ORM för Dart och Flutter, tidigare känt som Moor. Utvecklat av Simon Binder 2019 och har sedan dess gått igenom flera större versioner. Drift kompilerar Dart-frågor till SQL vid byggtid med hjälp av drift_dev och build_runner, vilket ger full typsäkerhet och eliminerar syntaxfel i SQL vid körning.
Till skillnad från Floor använder Drift sitt eget DSL (Domain-Specific Language) för att bygga frågor — utvecklaren skriver i Dart och generatorn översätter det till SQL. Detta låter IDE kontrollera syntax, autocompleta tabellfält och refaktorera datamodellen utan rädsla för att bryta frågan.
Enligt Drift (2024) används biblioteket i över 8000 Flutter-projekt. Det stöder alla populära plattformar: Android via sqflite, iOS via sqflite, web via sqlite3 WASM, desktop via sqlite3 native-drivrutin.
Moor bytte namn till Drift i version 2.0 (2022). Anledningen var namnkollision med andra projekt och en önskan att distansera sig från gammal kod. API förblev kompatibelt: för migration räcker det att byta import från moor till drift och uppdatera beroenden.
Drift erbjuder: inbyggda Stream-frågor med automatisk uppdatering vid dataändringar, stöd för transaktioner med återrullning, anpassade SQL-frågor via rawQuery, DAO-mönster för inkapsling av logik, plattformsoberoende migrationer och integration med Riverpod och BLoC via paketen drift_riverpod och drift_bloc.
Drift använder kodgenerering vid kompilering. Utvecklaren beskriver tabeller via @DataClass-annotationer eller Dart-klasser som utökar Table. Generatorn skapar hjälpklasser: Companion (för nullable fält vid insättning/uppdatering), DriftDatabase (startpunkt) och DAO-implementationer.
Drift utför inte SQLite-frågor direkt. Istället skriver utvecklaren i Dart: select(tasks).where(tasks.priority.greaterThan(3)).build(). Generatorn översätter detta till SQL, och vid körning skickar Drift helt enkelt den färdiga SQL-frågan till SQLite. Detta kombinerar bekvämligheten med Dart-syntax med prestandan från native SQL.
Drift stöder två frågelägen: DSL (rekommenderat) och raw SQL. DSL-frågor är säkrare att skriva — kompilatorn kontrollerar fältnamn, typer och kompatibilitet. Raw SQL behövs för komplexa frågor som inte täcks av DSL: fönsterfunktioner, rekursiva CTE, specifika SQLite-tillägg.
Drift erbjuder två sätt att skriva frågor: Dart DSL (native) och raw SQL (för komplexa fall). DSL är att föredra för 90% av scenarierna: det är säkrare, mer läsbart och stöder refaktorering. Raw SQL används endast när DSL inte täcker den önskade konstruktionen.
| Aspekt | Drift DSL | Raw SQL i Drift |
|---|---|---|
| Typsäkerhet | Full (kompilering) | Nej (runtime) |
| Autokomplettering | Ja (IDE) | Endast i .sql-filer |
| Refaktorering | Automatisk | Manuell sökning i strängar |
| Komplexa JOIN | Stöds | Full frihet |
| Fönsterfunktioner | Begränsat | Fullt stöd |
| Reaktivitet | Inbyggd (Stream) | Via .watch() |
Drift DSL — det primära arbetssättet. Det täcker SELECT, INSERT, UPDATE, DELETE, WHERE, ORDER BY, LIMIT, JOIN och gruppering. För alla typiska CRUD-frågor, använd DSL: det är kortare, säkrare och uppdaterar automatiskt Stream vid ändringar.
Raw SQL i Drift behövs för: anpassade SQLite-funktioner (FTS5, JSON1), komplexa underfrågor med EXISTS, INSERT OR REPLACE, massuppdateringar med CASE, samt för frågor där prestanda är kritisk och DSL inte genererar en optimal exekveringsplan. Raw SQL kan skrivas i .sql-filer med typstöd via drift_dev.
Drift använder klasser som utökar Table eller @DataClass-annotationen. Nedan är ett komplett exempel på en Task-modell med frågor via DSL, raw SQL och reaktiv uppdatering. Efter att ha kört build_runner är alla genererade klasser redo.
Klassen Tasks utökar Table och definierar kolumner. Varje kolumn är ett uttryck av typen Column<T>. Parametrar: withDefault() anger standardvärde, autoIncrement() — autoinkrement. Databasen är en abstrakt klass som utökar $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 genererar metoderna into(tasks).insert(), select(tasks), update(tasks) och delete(tasks) för tabeller. Alla operationer returnerar Future — arbete med SQLite är asynkront. För att spåra ändringar, använd .watch() istället för .get().
// Infoga
await into(tasks).insert(TasksCompanion.insert(
title: Value('Köp matvaror'),
priority: Value(3),
));
// Läs med filter
final highPriority = await (select(tasks)
..where((t) => t.priority.greaterThan(2))
..orderBy([(t) => OrderingTerm(expression: t.priority, mode: OrderingMode.desc)]))
.get();
// Reaktiv observation
select(tasks).watch().listen((tasksList) {
// tasksList — List, uppdateras vid varje tabelländring
updateUi(tasksList);
});
För komplexa frågor tillåter Drift att skriva raw SQL samtidigt som typningen bevaras. Metoden customSelect tar emot en frågesträng och returnerar ett typat resultat via kodgeneratorn. Detta tillvägagångssätt kombinerar SQL-flexibilitet med Drifts typsäkerhet.
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 stöder både automatiska migrationer (för enkla ändringar) och manuella (för komplexa transformationer). Databasversionen ställs in i AppDatabases konstruktor. Vid versionsmissmatchning tillämpar Drift alla väntande migrationer i följd.
För att lägga till en kolumn med standardvärde kan Drift generera en migration automatiskt via MigrationStrategy. Om ändringen inte påverkar befintlig data (lägga till nullable-fält), kan beforeOpen användas med versionskontroll och exekvering av ALTER TABLE.
För komplexa ändringar (ombenämning av tabell, sammanslagning av data, ändring av kolumntyp) kräver Drift manuell SQL-migration. Migrationerna anges via parametern migrations i databasklassen. Varje migration är ett objekt med from/to-nummer och SQL-frågor.
Drift stöder körning i testläge via NativeDatabase.memory(). Databasen i minnet skapas från grunden före varje test och förstörs efteråt. För mockning, använd paketet mocktail med mocked QueryExecutor. Drift tillhandahåller också DatabaseTestHelper för integrationstester med kontroll av migrationer och frågor.
Drift stöder DAO (Data Access Object) via abstrakta klasser med @DriftAccessor-annotationen. DAO kapslar in frågor till en eller flera tabeller och kan testas oberoende av databasen. Till skillnad från direkta frågor via Database tillåter DAO återanvändning av frågelogik mellan olika delar av applikationen och förenklar enhetstestning.
@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);
}));
}
}
Vanliga frågor
Drift använder eget DSL istället för SQL-strängar, vilket ger full typsäkerhet och autokomplettering i IDE. Floor använder SQL-strängar i @Query-annotationen. Drift stöder också fler plattformar (inklusive webben) och har inbyggd reaktivitet via Stream, medan Stream i Floor måste deklareras manuellt.
Ja, Drift stöder migrationer med databevarande. För att lägga till kolumner, använd addColumn i Migration. För komplexa transformationer (ombenämning, sammanslagning), skriv raw SQL inuti migrationen. Om ingen migration anges, återskapar Drift databasen med dataförlust vid schema-missmatchning.
Drift kräver kodgenerering via build_runner och drift_dev. Utan generering går det inte att skapa typade frågor. För små projekt stöder dock Drift sqlparser — manuell skrivning av SQL-filer med automatisk typning, men detta kräver fortfarande ett genereringssteg.
För integration med Riverpod, använd paketet drift_riverpod. Det tillhandahåller providers för Database, DAO och Stream-frågor. Exempel: final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — UI byggs automatiskt om vid dataändringar.
Drift har ingen inbyggd kryptering, men stöder anslutning av anpassade sqlite3-bibliotek med SEE (SQLite Encryption Extension). För mobila plattformar, använd sqflite_sqlcipher som QueryExecutor — Drift fungerar med alla SQLite-implementationer via den abstrakta QueryExecutor.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också