Drift (anteriormente Moor) é um ORM reativo para Flutter e Dart, construído sobre SQLite com seu próprio DSL para consultas. Ao contrário dos ORMs tradicionais, o Drift compila consultas Dart em SQL em tempo de compilação, eliminando erros de tempo de execução. De acordo com Drift Docs, 2024, o Drift gera até 40% mais código do que consultas SQL manuais, mas elimina completamente a escrita manual de SQL, substituindo-a por sintaxe Dart com segurança de tipos.
Principais pontos
Drift é um ORM para Dart e Flutter, anteriormente conhecido como Moor. Foi desenvolvido por Simon Binder em 2019 e desde então passou por várias versões principais. O Drift compila consultas Dart em SQL em tempo de compilação usando drift_dev e build_runner, fornecendo segurança total de tipos e eliminando erros de sintaxe SQL em tempo de execução.
Ao contrário do Floor, o Drift usa seu próprio DSL (Domínio-Specific Language) para construir consultas — o desenvolvedor escreve em Dart, e o gerador traduz para SQL. Isso permite que a IDE verifique a sintaxe, autocomplete campos de tabela e refatore o modelo de dados sem medo de quebrar consultas.
De acordo com Drift (2024), a biblioteca é usada em mais de 8000 projetos Flutter. Ela suporta todas as plataformas populares: Android via sqflite, iOS via sqflite, web via sqlite3 WASM, desktop via driver nativo sqlite3.
Moor foi renomeado para Drift na versão 2.0 (2022). O motivo foi um conflito de nomes com outros projetos e o desejo de se distanciar do código antigo. A API permaneceu compatível: para migrar, basta substituir o import de moor para drift e atualizar as dependências.
Drift fornece: consultas Stream integradas com atualização automática quando os dados mudam, suporte a transações com reversão, consultas SQL personalizadas via rawQuery, padrão DAO para encapsulamento de lógica, migrações multiplataforma e integração com Riverpod e BLoC através dos pacotes drift_riverpod e drift_bloc.
Drift usa geração de código em tempo de compilação. O desenvolvedor descreve as tabelas através de anotações @DataClass ou classes Dart que estendem Table. O gerador cria classes auxiliares: Companion (para campos anuláveis durante inserção/atualização), DriftDatabase (ponto de entrada) e implementações DAO.
O Drift não executa consultas SQLite diretamente. Em vez disso, o desenvolvedor escreve em Dart: select(tasks).where(tasks.priority.greaterThan(3)).build(). O gerador traduz isso para SQL, e em tempo de execução o Drift simplesmente envia a consulta SQL pronta para o SQLite. Isso combina a conveniência da sintaxe Dart com o desempenho do SQL nativo.
Drift suporta dois modos de consulta: DSL (recomendado) e SQL bruto. As consultas DSL são mais seguras de escrever — o compilador verifica nomes de campos, tipos e compatibilidade. O SQL bruto é necessário para consultas complexas não cobertas pelo DSL: funções de janela, CTEs recursivas, extensões SQLite específicas.
Drift oferece duas maneiras de escrever consultas: Dart DSL (nativo) e SQL bruto (para casos complexos). O DSL é preferível para 90% dos cenários: é mais seguro, mais legível e suporta refatoração. O SQL bruto é usado apenas quando o DSL não cobre a construção necessária.
| Aspecto | Drift DSL | SQL bruto no Drift |
|---|---|---|
| Segurança de tipos | Completa (compilação) | Não (execução) |
| Autocompletar | Sim (IDE) | Apenas em arquivos sql |
| Refatoração | Automática | Pesquisa manual em strings |
| JOINs complexos | Suportados | Liberdade total |
| Funções de janela | Limitado | Suporte completo |
| Reatividade | Integrada (Stream) | Através de .watch() |
Drift DSL é a forma principal de trabalho. Ele cobre SELECT, INSERT, UPDATE, DELETE, WHERE, ORDER BY, LIMIT, JOIN e agrupamento. Para todas as consultas CRUD típicas, use DSL: é mais curto, mais seguro e atualiza automaticamente o Stream nas alterações.
SQL bruto no Drift é necessário para: funções SQLite personalizadas (FTS5, JSON1), subconsultas complexas com EXISTS, INSERT OR REPLACE, UPDATE em massa com CASE, bem como consultas onde o desempenho é crítico e o DSL não gera o plano de execução ótimo. O SQL bruto pode ser escrito em arquivos .sql com suporte a tipagem via drift_dev.
Drift usa classes que estendem Table ou a anotação @DataClass. Abaixo está um exemplo completo do modelo Task com consultas via DSL, SQL bruto e atualização reativa. Após executar o build_runner, todas as classes geradas estão prontas.
A classe Tasks estende Table e define colunas. Cada coluna é uma expressão do tipo Column<T>. Parâmetros: withDefault() define um valor padrão, autoIncrement() define auto-incremento. O banco de dados é uma classe abstrata que estende $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 gera os métodos into(tasks).insert(), select(tasks), update(tasks) e delete(tasks) para as tabelas. Todas as operações retornam Future — trabalhar com SQLite é assíncrono. Para rastrear alterações, use .watch() em vez de .get().
// Inserir
await into(tasks).insert(TasksCompanion.insert(
title: Value('Comprar mantimentos'),
priority: Value(3),
));
// Leitura com filtro
final highPriority = await (select(tasks)
..where((t) => t.priority.greaterThan(2))
..orderBy([(t) => OrderingTerm(expression: t.priority, mode: OrderingMode.desc)]))
.get();
// Observação reativa
select(tasks).watch().listen((tasksList) {
// tasksList — List, atualiza a cada alteração da tabela
updateUi(tasksList);
});
Para consultas complexas, o Drift permite escrever SQL bruto mantendo a tipagem. O método customSelect recebe uma string de consulta e retorna um resultado tipado via gerador de código. Essa abordagem combina a flexibilidade do SQL com a segurança de tipos do 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 suporta tanto migrações automáticas (para alterações simples) quanto manuais (para transformações complexas). A versão do banco de dados é definida no construtor do AppDatabase. Se a versão não corresponder, o Drift aplica todas as migrações pendentes sequencialmente.
Para adicionar uma coluna com valor padrão, o Drift pode gerar uma migração automaticamente via MigrationStrategy. Se a alteração não quebrar os dados existentes (adicionar um campo anulável), você pode usar beforeOpen com verificação de versão e execução de ALTER TABLE.
Para alterações complexas (renomear uma tabela, mesclar dados, alterar o tipo de uma coluna), o Drift requer migração SQL manual. As migrações são especificadas através do parâmetro migrations na classe do banco de dados. Cada migração é um objeto com números from/to e consultas SQL.
Drift suporta execução em modo de teste via NativeDatabase.memory(). O banco de dados em memória é criado do zero antes de cada teste e destruído depois. Para simulação, use o pacote mocktail com um QueryExecutor simulado. O Drift também fornece DatabaseTestHelper para testes de integração com verificação de migrações e consultas.
Drift suporta DAO (Data Access Object) através de classes abstratas com a anotação @DriftAccessor. O DAO encapsula consultas a uma ou mais tabelas e pode ser testado separadamente do banco de dados. Ao contrário de consultas diretas através do Database, o DAO permite reutilizar a lógica de consultas entre diferentes partes da aplicação e simplifica os testes unitários.
@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);
}));
}
}
Perguntas frequentes
Drift usa seu próprio DSL em vez de strings SQL, fornecendo segurança total de tipos e autocompletar na IDE. O Floor usa strings SQL na anotação @Query. O Drift também suporta mais plataformas (incluindo web) e tem reatividade integrada via Stream, enquanto no Floor o Stream deve ser declarado manualmente.
Sim, o Drift suporta migrações com preservação de dados. Para adicionar colunas, use addColumn em Migration. Para transformações complexas (renomear, mesclar), escreva SQL bruto dentro da migração. Se nenhuma migração for especificada, o Drift recria o banco de dados com perda de dados quando o esquema não corresponde.
Drift requer geração de código via build_runner e drift_dev. Sem geração, é impossível criar consultas tipadas. No entanto, para projetos pequenos, o Drift suporta sqlparser — escrita manual de arquivos SQL com tipagem automática, mas isso ainda requer uma etapa de geração.
Para integração com Riverpod, use o pacote drift_riverpod. Ele fornece providers para Database, DAO e consultas Stream. Exemplo: final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — a UI é reconstruída automaticamente quando os dados mudam.
Drift não tem criptografia integrada, mas suporta conectar bibliotecas sqlite3 personalizadas com SEE (SQLite Encryption Extension). Para plataformas móveis, use sqflite_sqlcipher como QueryExecutor — o Drift funciona com qualquer implementação SQLite através do QueryExecutor abstrato.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também