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á


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.

DART
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);
}
TEXT 📖 Somente leitura
> **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


3. Anotações

(1) Definição e Uso de Anotações

▶ Exemplo

TEXT 📖 Somente leitura
> **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

DART
// 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'});
}
TEXT 📖 Somente leitura
> **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

TEXT 📖 Somente leitura
> **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)

DART
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);
}
TEXT 📖 Somente leitura
> **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.
⚠️ Nota: dart:mirrors está disponível apenas na Dart VM. Não é suportado no modo release do Flutter (compilação AOT) ou na Web.


4. API de Reflexão dart:mirrors

(1) Visão Geral das Capacidades de Reflexão

100%
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]
TEXT 📖 Somente leitura
> **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

TEXT 📖 Somente leitura
> **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

DART
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();
}
TEXT 📖 Somente leitura
> **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)
📌 Ponto-Chave: O ecossistema Dart escolheu "geração de código" em vez de "reflexão". Pacotes mainstream como json_serializable, freezed e dart_mappable são todos baseados em geração de código. A compilação AOT do Flutter naturalmente exclui a reflexão.


6. Mapeamento de Campos Orientado por Anotações

(1) Cenário do Bob: Mapeamento de Campos no DataPipeline

▶ Exemplo

TEXT 📖 Somente leitura
> **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)

DART
// 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');
}
TEXT 📖 Somente leitura
> **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

100%
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]
TEXT 📖 Somente leitura
> **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

TEXT 📖 Somente leitura
> **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

DART
// 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());
}
TEXT 📖 Somente leitura
> **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

DART
// ============================================
// 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');
}
TEXT 📖 Somente leitura
> **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:

TEXT 📖 Somente leitura
=== 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 --watch que 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


📝 Exercícios

  1. 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).
  2. 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.
  3. 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.

← Lição Anterior | Próxima Lição →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%