Drift (anciennement Moor) est un ORM réactif pour Flutter et Dart, construit sur SQLite avec son propre DSL pour les requêtes. Contrairement aux ORM traditionnels, Drift compile les requêtes Dart en SQL au moment de la construction, éliminant les erreurs d’exécution. Selon Drift Docs, 2024, Drift génère jusqu’à 40% plus de code que les requêtes SQL manuelles, mais élimine complètement l’écriture manuelle de SQL, en la remplaçant par une syntaxe Dart sûre pour les types.
Points clés
Drift est un ORM pour Dart et Flutter, anciennement connu sous le nom de Moor. Il a été développé par Simon Binder en 2019 et a depuis connu plusieurs versions majeures. Drift compile les requêtes Dart en SQL au moment de la construction en utilisant drift_dev et build_runner, offrant une sécurité totale des types et éliminant les erreurs de syntaxe SQL à l’exécution.
Contrairement à Floor, Drift utilise son propre DSL (langage spécifique au domaine) pour construire les requêtes — le développeur écrit en Dart, et le générateur le traduit en SQL. Cela permet à l’IDE de vérifier la syntaxe, d’autocompléter les champs de table et de refactoriser le modèle de données sans crainte de casser les requêtes.
Selon Drift (2024), la bibliothèque est utilisée dans plus de 8000 projets Flutter. Elle prend en charge toutes les plateformes populaires : Android via sqflite, iOS via sqflite, web via sqlite3 WASM, bureau via le pilote natif sqlite3.
Moor a été renommé en Drift dans la version 2.0 (2022). La raison était un conflit de noms avec d’autres projets et le souhait de se distancer de l’ancien code. L’API est restée compatible : pour migrer, il suffit de remplacer l’import de moor à drift et de mettre à jour les dépendances.
Drift fournit : des requêtes Stream intégrées avec mise à jour automatique lors des changements de données, la prise en charge des transactions avec rollback, des requêtes SQL personnalisées via rawQuery, le modèle DAO pour l’encapsulation de la logique, des migrations multiplateformes et l’intégration avec Riverpod et BLoC via les paquets drift_riverpod et drift_bloc.
Drift utilise la génération de code au moment de la compilation. Le développeur décrit les tables via des annotations @DataClass ou des classes Dart étendant Table. Le générateur crée des classes d’assistance : Companion (pour les champs nullable lors de l’insertion/mise à jour), DriftDatabase (point d’entrée) et des implémentations DAO.
Drift n’exécute pas les requêtes SQLite directement. Au lieu de cela, le développeur écrit en Dart : select(tasks).where(tasks.priority.greaterThan(3)).build(). Le générateur traduit cela en SQL, et à l’exécution, Drift envoie simplement la requête SQL prête à SQLite. Cela combine la commodité de la syntaxe Dart avec les performances du SQL natif.
Drift prend en charge deux modes de requête : DSL (recommandé) et SQL brut. Les requêtes DSL sont plus sûres à écrire — le compilateur vérifie les noms de champs, les types et la compatibilité. Le SQL brut est nécessaire pour les requêtes complexes non couvertes par le DSL : fonctions de fenêtre, CTE récursives, extensions SQLite spécifiques.
Drift offre deux façons d’écrire des requêtes : Dart DSL (natif) et SQL brut (pour les cas complexes). Le DSL est préférable pour 90% des scénarios : il est plus sûr, plus lisible et prend en charge le refactoring. Le SQL brut est utilisé uniquement lorsque le DSL ne couvre pas la construction nécessaire.
| Aspect | Drift DSL | SQL brut dans Drift |
|---|---|---|
| Sécurité des types | Complète (compilation) | Non (exécution) |
| Autocomplétion | Oui (IDE) | Uniquement dans les fichiers sql |
| Refactoring | Automatique | Recherche manuelle dans les chaînes |
| JOIN complexes | Pris en charge | Liberté totale |
| Fonctions de fenêtre | Limité | Prise en charge complète |
| Réactivité | Intégrée (Stream) | Via .watch() |
Drift DSL est le mode de travail principal. Il couvre SELECT, INSERT, UPDATE, DELETE, WHERE, ORDER BY, LIMIT, JOIN et le regroupement. Pour toutes les requêtes CRUD typiques, utilisez le DSL : il est plus court, plus sûr et met automatiquement à jour le Stream lors des modifications.
SQL brut dans Drift est nécessaire pour : les fonctions SQLite personnalisées (FTS5, JSON1), les sous-requêtes complexes avec EXISTS, INSERT OR REPLACE, les UPDATE massifs avec CASE, ainsi que les requêtes où les performances sont critiques et le DSL ne génère pas le plan d’exécution optimal. Le SQL brut peut être écrit dans des fichiers .sql avec prise en charge du typage via drift_dev.
Drift utilise des classes qui étendent Table ou l’annotation @DataClass. Voici un exemple complet du modèle Task avec des requêtes via DSL, SQL brut et mise à jour réactive. Après avoir exécuté build_runner, toutes les classes générées sont prêtes.
La classe Tasks étend Table et définit les colonnes. Chaque colonne est une expression de type Column<T>. Paramètres : withDefault() définit une valeur par défaut, autoIncrement() définit l’auto-incrémentation. La base de données est une classe abstraite qui étend $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 génère les méthodes into(tasks).insert(), select(tasks), update(tasks) et delete(tasks) pour les tables. Toutes les opérations retournent Future — travailler avec SQLite est asynchrone. Pour suivre les changements, utilisez .watch() au lieu de .get().
// Insérer
await into(tasks).insert(TasksCompanion.insert(
title: Value('Acheter des courses'),
priority: Value(3),
));
// Lecture avec filtre
final highPriority = await (select(tasks)
..where((t) => t.priority.greaterThan(2))
..orderBy([(t) => OrderingTerm(expression: t.priority, mode: OrderingMode.desc)]))
.get();
// Observation réactive
select(tasks).watch().listen((tasksList) {
// tasksList — List, mis à jour à chaque modification de la table
updateUi(tasksList);
});
Pour les requêtes complexes, Drift permet d’écrire du SQL brut tout en conservant le typage. La méthode customSelect prend une chaîne de requête et retourne un résultat typé via le générateur de code. Cette approche combine la flexibilité du SQL avec la sécurité des types de 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 prend en charge les migrations automatiques (pour les changements simples) et manuelles (pour les transformations complexes). La version de la base de données est définie dans le constructeur d’AppDatabase. Si la version ne correspond pas, Drift applique toutes les migrations en attente séquentiellement.
Pour ajouter une colonne avec une valeur par défaut, Drift peut générer automatiquement une migration via MigrationStrategy. Si le changement n’affecte pas les données existantes (ajout d’un champ nullable), vous pouvez utiliser beforeOpen avec vérification de version et exécution de ALTER TABLE.
Pour les changements complexes (renommer une table, fusionner des données, modifier le type d’une colonne), Drift nécessite une migration SQL manuelle. Les migrations sont spécifiées via le paramètre migrations dans la classe de base de données. Chaque migration est un objet avec des numéros from/to et des requêtes SQL.
Drift prend en charge l’exécution en mode test via NativeDatabase.memory(). La base de données en mémoire est créée à partir de zéro avant chaque test et détruite après. Pour le mock, utilisez le paquet mocktail avec un QueryExecutor simulé. Drift fournit également DatabaseTestHelper pour les tests d’intégration avec vérification des migrations et des requêtes.
Drift prend en charge DAO (Data Access Object) via des classes abstraites avec l’annotation @DriftAccessor. DAO encapsule les requêtes vers une ou plusieurs tables et peut être testé séparément de la base de données. Contrairement aux requêtes directes via Database, DAO permet de réutiliser la logique de requête entre différentes parties de l’application et simplifie les tests unitaires.
@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);
}));
}
}
Foire aux questions
Drift utilise son propre DSL au lieu de chaînes SQL, offrant une sécurité totale des types et l’autocomplétion dans l’IDE. Floor utilise des chaînes SQL dans l’annotation @Query. Drift prend également en charge plus de plateformes (y compris le web) et dispose d’une réactivité intégrée via Stream, tandis que dans Floor, le Stream doit être déclaré manuellement.
Oui, Drift prend en charge les migrations avec préservation des données. Pour ajouter des colonnes, utilisez addColumn dans Migration. Pour les transformations complexes (renommage, fusion), écrivez du SQL brut dans la migration. Si aucune migration n’est spécifiée, Drift recrée la base de données avec perte de données lorsque le schéma ne correspond pas.
Drift nécessite la génération de code via build_runner et drift_dev. Sans génération, il est impossible de créer des requêtes typées. Cependant, pour les petits projets, Drift prend en charge sqlparser — écriture manuelle de fichiers SQL avec typage automatique, mais cela nécessite tout de même une étape de génération.
Pour l’intégration avec Riverpod, utilisez le paquet drift_riverpod. Il fournit des providers pour Database, DAO et les requêtes Stream. Exemple : final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — l’UI se reconstruit automatiquement lorsque les données changent.
Drift n’a pas de chiffrement intégré, mais il prend en charge la connexion de bibliothèques sqlite3 personnalisées avec SEE (SQLite Encryption Extension). Pour les plateformes mobiles, utilisez sqflite_sqlcipher comme QueryExecutor — Drift fonctionne avec toute implémentation SQLite via le QueryExecutor abstrait.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi