Drift (sebelumnya Moor) — ORM reaktif untuk Flutter dan Dart, dibangun di atas SQLite dengan DSL sendiri untuk query. Berbeda dengan ORM tradisional, Drift mengkompilasi query Dart ke SQL pada tahap build, menghilangkan kesalahan runtime. Menurut Drift Docs, 2024, Drift menghasilkan hingga 40% lebih banyak kode daripada query SQL manual, tetapi sepenuhnya menghilangkan penulisan SQL manual, menggantikannya dengan sintaks Dart yang type-safe.
Utama
Drift — ORM untuk Dart dan Flutter, sebelumnya dikenal sebagai Moor. Dikembangkan oleh Simon Binder pada tahun 2019 dan telah melalui beberapa versi utama. Drift mengkompilasi query Dart ke SQL pada tahap build menggunakan drift_dev dan build_runner, memberikan keamanan tipe penuh dan menghilangkan kesalahan sintaks SQL saat runtime.
Berbeda dengan Floor, Drift menggunakan DSL (Domain-Specific Language) sendiri untuk membangun query — pengembang menulis di Dart, dan generator menerjemahkannya ke SQL. Ini memungkinkan IDE memeriksa sintaks, melengkapi otomatis field tabel, dan merefaktor model data tanpa takut merusak query.
Menurut Drift (2024), library ini digunakan di lebih dari 8000 proyek Flutter. Mendukung semua platform populer: Android melalui sqflite, iOS melalui sqflite, web melalui sqlite3 WASM, desktop melalui driver sqlite3 native.
Moor diganti nama menjadi Drift pada versi 2.0 (2022). Alasan — konflik nama dengan proyek lain dan keinginan untuk menjauh dari kode lama. API tetap kompatibel: untuk migrasi cukup mengganti import dari moor ke drift dan memperbarui dependensi.
Drift menyediakan: query Stream bawaan dengan pembaruan otomatis saat data berubah, dukungan transaksi dengan rollback, query SQL kustom melalui rawQuery, pola DAO untuk enkapsulasi logika, migrasi lintas platform, dan integrasi dengan Riverpod dan BLoC melalui paket drift_riverpod dan drift_bloc.
Drift menggunakan generasi kode pada tahap kompilasi. Pengembang mendeskripsikan tabel melalui anotasi @DataClass atau kelas Dart yang memperluas Table. Generator membuat kelas pembantu: Companion (untuk field nullable saat insert/update), DriftDatabase (titik masuk) dan implementasi DAO.
Query SQLite tidak dijalankan langsung oleh Drift. Sebagai gantinya, pengembang menulis di Dart: select(tasks).where(tasks.priority.greaterThan(3)).build(). Generator menerjemahkan ini ke SQL, dan saat eksekusi Drift hanya mengirim query SQL yang sudah siap ke SQLite. Ini menggabungkan kenyamanan sintaks Dart dengan performa SQL native.
Drift mendukung dua mode query: DSL (direkomendasikan) dan raw SQL. Query DSL lebih aman — kompiler memeriksa nama field, tipe, dan kompatibilitas. Raw SQL diperlukan untuk query kompleks yang tidak dicakup oleh DSL: fungsi window, CTE rekursif, ekstensi SQLite spesifik.
Drift menawarkan dua cara menulis query: Dart DSL (native) dan raw SQL (untuk kasus kompleks). DSL lebih disukai dalam 90% skenario: lebih aman, lebih mudah dibaca, dan mendukung refactoring. Raw SQL hanya digunakan ketika DSL tidak mencakup konstruksi yang diperlukan.
| Aspek | Drift DSL | Raw SQL di Drift |
|---|---|---|
| Keamanan tipe | Penuh (kompilasi) | Tidak (runtime) |
| Auto-complete | Ya (IDE) | Hanya di file sql |
| Refactoring | Otomatis | Pencarian manual di string |
| JOIN kompleks | Didukung | Kebebasan penuh |
| Fungsi window | Terbatas | Dukungan penuh |
| Reaktivitas | Bawaan (Stream) | Melalui .watch() |
Drift DSL — cara kerja utama. Mencakup SELECT, INSERT, UPDATE, DELETE, WHERE, ORDER BY, LIMIT, JOIN dan pengelompokan. Untuk semua query CRUD tipikal, gunakan DSL: lebih pendek, lebih aman, dan secara otomatis memperbarui Stream saat ada perubahan.
Raw SQL di Drift diperlukan untuk: fungsi SQLite kustom (FTS5, JSON1), subquery kompleks dengan EXISTS, INSERT OR REPLACE, UPDATE massal dengan CASE, serta query dimana performa kritis dan DSL tidak menghasilkan rencana eksekusi optimal. Raw SQL dapat ditulis di file .sql dengan dukungan typing melalui drift_dev.
Drift menggunakan kelas yang memperluas Table atau anotasi @DataClass. Di bawah ini — contoh lengkap model Task dengan query melalui DSL, raw SQL dan pembaruan reaktif. Setelah menjalankan build_runner, semua kelas yang dihasilkan siap digunakan.
Kelas Tasks memperluas Table dan mendefinisikan kolom. Setiap kolom adalah ekspresi bertipe Column
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 menghasilkan metode into(tasks).insert(), select(tasks), update(tasks) dan delete(tasks) untuk tabel. Semua operasi mengembalikan Future — bekerja dengan SQLite bersifat asinkron. Untuk melacak perubahan, gunakan .watch() sebagai ganti .get().
// Menulis
await into(tasks).insert(TasksCompanion.insert(
title: Value('Beli produk'),
priority: Value(3),
));
// Membaca dengan filter
final highPriority = await (select(tasks)
..where((t) => t.priority.greaterThan(2))
..orderBy([(t) => OrderingTerm(expression: t.priority, mode: OrderingMode.desc)]))
.get();
// Pengamatan reaktif
select(tasks).watch().listen((tasksList) {
// tasksList — List, diperbarui setiap kali tabel berubah
updateUi(tasksList);
});
Untuk query kompleks, Drift memungkinkan menulis raw SQL sambil mempertahankan typing. Metode customSelect menerima string query dan mengembalikan hasil yang diketik melalui generator kode. Pendekatan ini menggabungkan fleksibilitas SQL dengan keamanan tipe 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 mendukung migrasi otomatis (untuk perubahan sederhana) dan manual (untuk transformasi kompleks). Versi database diatur di konstruktor AppDatabase. Ketika versi tidak cocok, Drift menerapkan semua migrasi yang belum ditutup secara berurutan.
Untuk menambahkan kolom dengan nilai default, Drift dapat menghasilkan migrasi otomatis melalui MigrationStrategy. Jika perubahan tidak melanggar data yang ada (menambahkan field nullable), beforeOpen dapat digunakan dengan pemeriksaan versi dan eksekusi ALTER TABLE.
Untuk perubahan kompleks (mengganti nama tabel, menggabungkan data, mengubah tipe kolom) Drift memerlukan migrasi SQL manual. Migrasi ditentukan melalui parameter migrations di kelas database. Setiap migrasi adalah objek dengan nomor from/to dan query SQL.
Drift mendukung eksekusi dalam mode pengujian melalui NativeDatabase.memory(). Database dalam memori dibuat dari awal sebelum setiap pengujian dan dihancurkan setelahnya. Untuk mocking, gunakan paket mocktail dengan QueryExecutor yang di-mock. Drift juga menyediakan DatabaseTestHelper untuk pengujian integrasi dengan verifikasi migrasi dan query.
Drift mendukung DAO (Data Access Object) melalui kelas abstrak dengan anotasi @DriftAccessor. DAO mengenkapsulasi query ke satu atau beberapa tabel dan dapat diuji terpisah dari database. Berbeda dengan query langsung melalui Database, DAO memungkinkan penggunaan kembali logika query antar bagian aplikasi yang berbeda dan menyederhanakan pengujian modular.
@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);
}));
}
}
Pertanyaan yang sering diajukan
Drift menggunakan DSL sendiri sebagai pengganti string SQL, memberikan keamanan tipe penuh dan auto-complete di IDE. Floor menggunakan string SQL dalam anotasi @Query. Drift juga mendukung lebih banyak platform (termasuk web) dan memiliki reaktivitas bawaan melalui Stream, sedangkan di Floor Stream harus dideklarasikan secara manual.
Ya, Drift mendukung migrasi dengan mempertahankan data. Untuk menambahkan kolom, gunakan addColumn di Migration. Untuk transformasi kompleks (penggantian nama, penggabungan) tulis raw SQL di dalam migrasi. Jika migrasi tidak ditentukan, Drift membuat ulang database dengan kehilangan data saat ketidakcocokan skema.
Drift membutuhkan generasi kode melalui build_runner dan drift_dev. Tanpa generasi, query yang diketik tidak dapat dibuat. Namun, untuk proyek kecil Drift mendukung sqlparser — penulisan manual file SQL dengan typing otomatis, tetapi ini masih memerlukan tahap generasi.
Untuk integrasi dengan Riverpod, gunakan paket drift_riverpod. Ini menyediakan provider untuk Database, DAO dan query Stream. Contoh: final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — UI secara otomatis dibangun ulang saat data berubah.
Drift tidak memiliki enkripsi bawaan, tetapi mendukung menghubungkan library sqlite3 kustom dengan SEE (SQLite Encryption Extension). Untuk platform mobile, gunakan sqflite_sqlcipher sebagai QueryExecutor — Drift bekerja dengan implementasi SQLite apa pun melalui QueryExecutor abstrak.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga