Dart: Metaprogramação e Reflexão em Dart — Anotações e Código
Última atualização: 2026-08-26
Metaprogramação é escrever código que escreve código — automatizando trabalho repetitivo para que desenvolvedores possam focar na lógica de negócio.
1. O que Você Aprenderá
- API de reflexão dart:mirrors (limitação apenas para VM)
- Anotações: Definição e leitura
- Trade-offs entre geração de código e reflexão
- Filosofia de design sem reflexão e a escolha do Flutter
- Cenário do Bob: Mapeamento de campos orientado por anotações no DataPipeline
2. A História Real de um Desenvolvedor
(1) O Problema: Código de Serialização Manual Representa 60% do Esforço de Desenvolvimento
O DataPipeline do Bob tem 20 classes de modelo de dados, cada uma exigindo métodos fromJson/toJson. Escrever manualmente o código de serialização para 20 classes levou 3 dias e era propenso a erros — um único nome de campo digitado incorretamente causou a falha de análise de 100.000 registros.
(2) A Solução com Geração de Código
Dart escolheu geração de código em vez de reflexão. Ao anotar classes de modelo, o build_runner gera automaticamente o código de serialização.
import 'package:json_annotation/json_annotation.dart';
part 'order.g.dart';
@JsonSerializable()
class Order {
final String id;
final double amount;
Order({required this.id, required this.amount});
factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
Map<String, dynamic> toJson() => _$OrderToJson(this);
}
> **Saída:** Execute em um DartPad local ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
(3) Benefícios
- Código de serialização para 20 classes gerado automaticamente, de 3 dias para 30 minutos
- Nomes de campos digitados incorretamente são detectados em tempo de compilação
- Compatível com compilação AOT, suportando Flutter/Web em todas as plataformas
3. Anotações
(1) Definição e Uso de Anotações
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
: Anotações Personalizadas
// Definir uma anotação personalizada
class Column {
final String name;
final bool nullable;
final String? defaultValue;
const Column({
required this.name,
this.nullable = false,
this.defaultValue,
});
}
class Table {
final String name;
const Table(this.name);
}
// Aplicar anotações a uma classe
@Table('orders')
class Order {
@Column(name: 'order_id')
final String id;
@Column(name: 'amount', nullable: false)
final double amount;
@Column(name: 'status', defaultValue: 'pending')
final String status;
Order({required this.id, required this.amount, this.status = 'pending'});
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
: Lendo Anotações Usando Reflexão (apenas VM)
import 'dart:mirrors';
// Ler anotações usando reflexão (apenas VM!)
void printTableInfo(Type type) {
final classMirror = reflectClass(type);
// Ler anotações de nível de classe
for (final metadata in classMirror.metadata) {
if (metadata.reflectee is Table) {
final table = metadata.reflectee as Table;
print('Table: ${table.name}');
}
}
// Ler anotações de nível de campo
classMirror.declarations.forEach((key, declaration) {
if (declaration is VariableMirror) {
for (final metadata in declaration.metadata) {
if (metadata.reflectee is Column) {
final column = metadata.reflectee as Column;
print(' ${declaration.simpleName}: ${column.name} '
'(nullable: ${column.nullable}, default: ${column.defaultValue})');
}
}
}
});
}
void main() {
printTableInfo(Order);
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
4. API de Reflexão dart:mirrors
(1) Visão Geral das Capacidades de Reflexão
graph TD A[Metaprogramação] --> B[Reflexão dart:mirrors] A --> C[Anotações + Geração de Código] B --> B1[Flexível em runtime] B --> B2[Apenas VM / AOT indisponível] C --> C1[Geração em tempo de compilação] C --> C2[Compatível com AOT / Amigável ao Flutter]
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
: Uso da API de Reflexão
import 'dart:mirrors';
class Product {
final String name;
final double price;
String category;
Product({required this.name, required this.price, this.category = 'General'});
String formatPrice() => '\$${price.toStringAsFixed(2)} USD';
double applyDiscount(double rate) => price * (1 - rate);
}
void reflectOnProduct() {
final mirror = reflectClass(Product);
// Listar todos os métodos de instância
print('Methods:');
mirror.instanceMembers.forEach((name, member) {
if (member is MethodMirror && !member.isConstructor && !member.isStatic) {
print(' $name: ${member.returnType.reflectedType}');
}
});
// Criar instância via reflexão
final instance = mirror.newInstance(
Symbol(''),
[],
{#name: 'Laptop', #price: 1299.99, #category: 'Electronics'},
);
// Invocar método via reflexão
final formatted = instance.invoke(#formatPrice, []);
print('Formatted: ${formatted.reflectee}');
final discounted = instance.invoke(#applyDiscount, [0.1]);
print('Discounted: \$${discounted.reflectee.toStringAsFixed(2)} USD');
}
void main() {
reflectOnProduct();
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
| Capacidade de Reflexão | API | Descrição |
|---|---|---|
| Obter informação de classe | reflectClass(Type) |
Nome da classe, métodos, campos |
| Criar instância | newInstance() |
Instanciação dinâmica |
| Invocar método | invoke() |
Invocação dinâmica de método |
| Ler campo | getField() |
Acesso dinâmico a propriedade |
| Ler anotações | .metadata |
Obter metadados |
5. Geração de Código vs. Reflexão
(1) Comparação e Trade-offs
| Dimensão | Reflexão dart:mirrors | Geração de Código (build_runner) |
|---|---|---|
| Runtime | Flexível, decisões em runtime | Determinado em tempo de compilação |
| Compatível com AOT | Incompatível | Compatível |
| Suporte Flutter | Indisponível no modo release | Suportado em todos os modos |
| Suporte Web | Indisponível | Suportado |
| Desempenho | Sobrecarga em runtime | Zero sobrecarga em runtime |
| Experiência do desenvolvedor | Sem etapa de geração | Requer etapa build_runner |
| Depuração | Difícil (despacho dinâmico) | Simples (código gerado é legível) |
6. Mapeamento de Campos Orientado por Anotações
(1) Cenário do Bob: Mapeamento de Campos no DataPipeline
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
: Processador de Anotações Manual (Simulando Geração de Código)
// Anotações personalizadas para mapeamento de campos
class FieldMapping {
final String csvColumn;
final String? defaultValue;
final bool required;
const FieldMapping({
required this.csvColumn,
this.defaultValue,
this.required = true,
});
}
class ModelMapping {
final String tableName;
const ModelMapping(this.tableName);
}
// Modelo com anotações de mapeamento de campos
@ModelMapping('customers')
class Customer {
@FieldMapping(csvColumn: 'customer_id')
final String id;
@FieldMapping(csvColumn: 'customer_name', defaultValue: 'Unknown')
final String name;
@FieldMapping(csvColumn: 'email', required: false)
final String? email;
@FieldMapping(csvColumn: 'total_spent', defaultValue: '0')
final double totalSpent;
Customer({
required this.id,
required this.name,
this.email,
this.totalSpent = 0,
});
// Mapeamento manual (em projeto real, isso seria gerado)
static Customer fromCsvMap(Map<String, String> csvRow) {
return Customer(
id: csvRow['customer_id'] ?? '',
name: csvRow['customer_name'] ?? 'Unknown',
email: csvRow['email'],
totalSpent: double.tryParse(csvRow['total_spent'] ?? '0') ?? 0,
);
}
}
void main() {
final csvRow = {
'customer_id': 'CUST-001',
'customer_name': 'Alice',
'email': 'alice@example.com',
'total_spent': '52500.75',
};
final customer = Customer.fromCsvMap(csvRow);
print('Customer: ${customer.id}, ${customer.name}');
print('Email: ${customer.email ?? "N/A"}');
print('Total: \$${customer.totalSpent.toStringAsFixed(2)} USD');
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
7. Filosofia de Design Sem Reflexão
(1) Por Que Dart Escolheu Não Usar Reflexão
graph TD A[Reflexão vs Geração de Código] --> B[Reflexão] A --> C[Geração de Código] B --> B1[Flexibilidade em runtime] B --> B2[Incompatível com AOT] B --> B3[Bloqueado no Flutter/Web] B --> B4[Sobrecarga de desempenho] C --> C1[Certeza em tempo de compilação] C --> C2[Compatível com AOT] C --> C3[Amigável ao Flutter/Web] C --> C4[Custo zero em runtime]
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
| Princípio de Design | Descrição |
|---|---|
| AOT primeiro | Flutter release usa compilação AOT; reflexão está indisponível |
| Tree shaking | Compilador remove código não utilizado; reflexão impede tree shaking |
| Desempenho primeiro | Reflexão tem sobrecarga em runtime; geração de código tem zero sobrecarga |
| Segurança de tipos | Geração de código mantém segurança de tipos; reflexão perde verificação de tipos |
▶ Exemplo
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
: Geração de Código como Alternativa à Reflexão
// Em vez de parsing JSON baseado em reflexão:
// dynamic parseJson(Map<String, dynamic> json, Type type) { ... } // RUIM
// Use abordagem baseada em geração de código:
// 1. Definir modelo com anotação
// @JsonSerializable()
// class Order { ... }
// 2. Executar: dart run build_runner build
// 3. Código gerado fornece parsing tipo-seguro:
// factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
// Versão manual (simulando código gerado)
class Order {
final String id;
final double amount;
Order({required this.id, required this.amount});
// "Gerado" fromJson
factory Order.fromJson(Map<String, dynamic> json) => Order(
id: json['id'] as String,
amount: (json['amount'] as num).toDouble(),
);
// "Gerado" toJson
Map<String, dynamic> toJson() => {
'id': id,
'amount': amount,
};
}
void main() {
final json = {'id': 'ORD-001', 'amount': 1500.0};
final order = Order.fromJson(json); // Tipo-seguro!
print(order.toJson());
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
8. Exemplo Completo: Mapeamento Orientado por Anotações do DataPipeline
// ============================================
// DataPipeline Annotation-Driven Mapping
// Simula geração de código para CSV-to-Model
// ============================================
// Anotações
class CsvField {
final String column;
final String? defaultValue;
final bool required;
const CsvField({
required this.column,
this.defaultValue,
this.required = true,
});
}
class CsvModel {
final String fileName;
const CsvModel(this.fileName);
}
// Classes de modelo
@CsvModel('orders')
class Order {
@CsvField(column: 'order_id')
final String id;
@CsvField(column: 'total_amount', defaultValue: '0')
final double amount;
@CsvField(column: 'order_status', defaultValue: 'pending')
final String status;
@CsvField(column: 'category', required: false)
final String? category;
Order({
required this.id,
required this.amount,
this.status = 'pending',
this.category,
});
// Em projeto real, isso seria gerado pelo build_runner
static Order fromCsvRow(Map<String, String> row) => Order(
id: row['order_id'] ?? '',
amount: double.tryParse(row['total_amount'] ?? '0') ?? 0,
status: row['order_status'] ?? 'pending',
category: row['category'],
);
double get tax => amount * 0.08;
double get total => amount + tax;
@override
String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status${category != null ? ", $category" : ""})';
}
// Mapper (simulando código gerado)
class CsvMapper``<T>`` {
final T Function(Map<String, String>) fromCsvRow;
CsvMapper(this.fromCsvRow);
List``<T>`` mapAll(List<Map<String, String>> rows) =>
rows.map(fromCsvRow).toList();
(List``<T>`` valid, List<(int, String)> errors) mapSafe(
List<Map<String, String>> rows) {
final valid = ``<T>``[];
final errors = <(int, String)>[];
for (var i = 0; i < rows.length; i++) {
try {
valid.add(fromCsvRow(rows[i]));
} catch (e) {
errors.add((i + 1, e.toString()));
}
}
return (valid, errors);
}
}
void main() {
// Dados CSV simulados (já analisados em maps)
final csvRows = <Map<String, String>>[
{'order_id': 'ORD-001', 'total_amount': '1500.00', 'order_status': 'completed', 'category': 'Electronics'},
{'order_id': 'ORD-002', 'total_amount': '3200.50', 'order_status': 'completed', 'category': 'Electronics'},
{'order_id': 'ORD-003', 'total_amount': '890.00', 'order_status': 'pending', 'category': 'Clothing'},
{'order_id': 'ORD-004', 'total_amount': '50.00', 'order_status': 'completed'},
];
// Mapear para objetos de modelo
final mapper = CsvMapper``<Order>``(Order.fromCsvRow);
final (valid, errors) = mapper.mapSafe(csvRows);
print('=== DataPipeline CSV Import Report ===');
print('Rows: ${csvRows.length}');
print('Valid: ${valid.length}');
print('Errors: ${errors.length}');
if (errors.isNotEmpty) {
print('\nErrors:');
for (final (line, msg) in errors) {
print(' Line $line: $msg');
}
}
// Processar pedidos válidos
double totalRevenue = 0;
for (final order in valid) {
if (order.status == 'completed') {
totalRevenue += order.total;
}
print(' $order');
}
print('\nRevenue (completed): \$${totalRevenue.toStringAsFixed(2)} USD');
}
> **Saída:** Execute em um DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
Saída:
=== DataPipeline CSV Import Report ===
Rows: 4
Valid: 4
Errors: 0
Order(ORD-001, $1500.00, completed, Electronics)
Order(ORD-002, $3200.50, completed, Electronics)
Order(ORD-003, $890.00, pending, Clothing)
Order(ORD-004, $50.00, completed)
Revenue (completed): $5132.54 USD
❓ Perguntas Frequentes
P: Por que o Flutter não suporta dart:mirrors? R: O Flutter usa compilação AOT para código nativo. AOT exige que todas as informações de tipo sejam determinadas em tempo de compilação. Reflexão procura tipos dinamicamente em runtime, o que conflita com o tree shaking e as otimizações de compilação do AOT.
P: Para que servem as anotações por si mesmas? R: Anotações por si mesmas não executam nenhuma lógica; elas são apenas metadados. Elas precisam ser lidas por reflexão (VM) ou processadas por um gerador de código (build_runner) para ter efeito.
P: Preciso re-executar o build_runner toda vez que alterar o código? R: Sim, mas existe um modo
--watchque monitora alterações de arquivos e regenera automaticamente. Use o modo watch durante o desenvolvimento e o modo build antes do release.
P: Qual a diferença entre json_serializable e escrever fromJson manualmente? R: json_serializable gera código automaticamente, evitando erros de escrita manual, e suporta objetos aninhados e conversões personalizadas. Escrita manual é simples mas propensa a erros, e objetos aninhados se tornam ainda mais dolorosos.
P: Dart suportará macros no futuro? R: A equipe Dart está desenvolvendo um sistema de macros (macro package), com o objetivo de substituir parte da funcionalidade do build_runner para uma melhor experiência do desenvolvedor. No entanto, ainda não está estável.
P: A geração de código aumenta o tamanho do pacote? R: Sim, porque o código gerado é incluído na saída da compilação. No entanto, reflexão também aumenta o tamanho (impedindo tree shaking); a diferença é mínima.
P: Como depurar código gerado? R: Você pode abrir e ler os arquivos .g.dart gerados diretamente para depuração. O código gerado pelo build_runner é código Dart padrão; você pode definir breakpoints.
📖 Resumo
- dart:mirrors fornece capacidades de reflexão em runtime mas é limitado ao ambiente VM; está indisponível para Flutter AOT e Web
- Anotações são marcadores de metadados que requerem reflexão ou um gerador de código para serem consumidos
- O ecossistema Dart escolheu "geração de código" em vez de "reflexão" para compatibilidade com AOT/Flutter/Web
- A filosofia de design sem reflexão: determinismo em tempo de compilação, zero sobrecarga em runtime, preservando tree shaking
- O DataPipeline usa anotações @CsvField para marcar mapeamento de campos, simulando geração de código
📝 Exercícios
- Básico (Dificuldade ⭐): Defina 3 anotações personalizadas (@ApiEndpoint, @Required, @DefaultValue) e aplique-as a uma classe. Use dart:mirrors para ler as informações de anotação (nota: só pode executar na VM).
- Intermediário (Dificuldade ⭐⭐): Crie um projeto json_serializable, gere código fromJson/toJson para uma classe Order com 5 campos. Visualize o arquivo .g.dart gerado e entenda a lógica de geração.
- Desafio (Dificuldade ⭐⭐⭐): Design um gerador de código simples: leia definições de classe anotadas com @CsvField e gere um método estático fromCsvRow. Dica: você pode usar o pacote source_gen ou templates de string simples.