Dart: Enums e Métodos de Extensão em Dart — Enhanced Enums

Última atualização: 2026-08-26

Enums dão nomes a estados finitos, extensões dão novas capacidades a tipos antigos — ambos são ferramentas poderosas para aprimorar o código sem modificar sua fonte.

1. O que Você Aprenderá


2. A História Real de um Desenvolvedor

(1) O Problema: Usar Strings para Simular Estados Gera Erros de Digitação

Alice usou strings para representar status de pedidos em seu código: 'pending', 'shipped', 'delivered'. Um erro de digitação 'shiped' não foi detectado pelo compilador, fazendo com que o pedido permanecesse preso no status "não enviado", resultando em 200 reclamações de clientes. Ela também frequentemente escrevia verificações como if (status == 'pending' || status == 'processing'), que eram fáceis de esquecer.

(2) A Solução com Enums

Os Enhanced Enums do Dart dão a cada estado um nome tipo-seguro, junto com propriedades e métodos anexados. Expressões switch garantem exaustividade; omitir um estado causa um erro do compilador.

DART
enum OrderStatus {
  pending(label: 'Awaiting Processing', isFinal: false),
  shipped(label: 'In Transit', isFinal: false),
  delivered(label: 'Completed', isFinal: true),
  cancelled(label: 'Cancelled', isFinal: true);

  final String label;
  final bool isFinal;

  const OrderStatus({required this.label, required this.isFinal});
}

// Switch exaustivo - compilador verifica todos os casos
String handle(OrderStatus status) => switch (status) {
  OrderStatus.pending => 'Queue for processing',
  OrderStatus.shipped => 'Track shipment',
  OrderStatus.delivered => 'Send survey',
  OrderStatus.cancelled => 'Process refund',
};
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `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. Enhanced Enums

(1) Enums Básicos

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `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.

: Enum Simples

DART
enum OutputFormat {
  json,
  csv,
  html,
}

void main() {
  final format = OutputFormat.json;

  // Valores do enum
  print(format.name);          // json
  print(format.index);         // 0
  print(OutputFormat.values);  // [OutputFormat.json, OutputFormat.csv, OutputFormat.html]

  // Analisar a partir de string
  final parsed = OutputFormat.values.byName('csv');
  print(parsed);  // OutputFormat.csv
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `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.

(2) Enhanced Enums

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `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.

: Enhanced Enum com Propriedades

DART
enum OrderStatus {
  pending(label: 'Awaiting Processing', isFinal: false, priority: 1),
  processing(label: 'Being Processed', isFinal: false, priority: 2),
  shipped(label: 'In Transit', isFinal: false, priority: 3),
  delivered(label: 'Completed', isFinal: true, priority: 0),
  cancelled(label: 'Cancelled', isFinal: true, priority: 0);

  final String label;
  final bool isFinal;
  final int priority;

  const OrderStatus({required this.label, required this.isFinal, required this.priority});

  bool get isActive => !isFinal;

  String get displayName => '${name.toUpperCase()} - $label';
}

void main() {
  final status = OrderStatus.shipped;

  print(status.label);       // In Transit
  print(status.isFinal);     // false
  print(status.isActive);    // true
  print(status.displayName); // SHIPPED - In Transit
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `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 localmente no DartPad ou via `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.

: Enhanced Enum com Métodos

DART
enum TaxCategory {
  standard(rate: 0.08, label: 'Standard Rate'),
  reduced(rate: 0.05, label: 'Reduced Rate'),
  zero(rate: 0.0, label: 'Zero Rate'),
  exempt(rate: 0.0, label: 'Tax Exempt');

  final double rate;
  final String label;

  const TaxCategory({required this.rate, required this.label});

  double calculate(double amount) => amount * rate;

  double applyTo(double amount) => amount * (1 + rate);

  String formatRate() => '${(rate * 100).toStringAsFixed(1)}%';
}

void main() {
  final tax = TaxCategory.standard;
  print(tax.calculate(1500.0));   // 120.0
  print(tax.applyTo(1500.0));     // 1620.0
  print(tax.formatRate());        // 8.0%

  // Todas as categorias
  for (final cat in TaxCategory.values) {
    print('${cat.label}: ${cat.formatRate()}');
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `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. Enums com Switch

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `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.

: Switch Exaustivo

DART
enum DataSourceType {
  api,
  file,
  database,
}

String describeSource(DataSourceType type) => switch (type) {
  DataSourceType.api => 'REST API endpoint',
  DataSourceType.file => 'Local file system',
  DataSourceType.database => 'SQL database connection',
};

// Com verificação exaustiva - compilador força todos os casos
bool canRetry(DataSourceType type) => switch (type) {
  DataSourceType.api => true,      // API pode tentar novamente
  DataSourceType.file => false,    // Erros de arquivo precisam de correção manual
  DataSourceType.database => true, // DB pode tentar novamente com backoff
};

void main() {
  print(describeSource(DataSourceType.api));  // REST API endpoint
  print(canRetry(DataSourceType.file));       // false
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `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.
Recurso if-else switch statement switch expression
Verificação de exaustividade Não Não Sim (para enums)
Garantia em tempo de compilação Não Não Sim
Ao adicionar novo valor de enum Pode ser esquecido Pode ser esquecido Erro de compilação

5. Extension Methods

(1) Extensões Básicas

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `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.

: Extensão de String

DART
extension StringCurrency on String {
  String toUSD() => '\$$this USD';
  String toEUR() => '€${this} EUR';

  String truncate(int maxLength) =>
      length <= maxLength ? this : '${substring(0, maxLength)}...';

  String get capitalized =>
      isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';
}

void main() {
  print('1500.00'.toUSD());         // $1500.00 USD
  print('1200.00'.toEUR());        // €1200.00 EUR
  print('Very long product name'.truncate(10));  // Very long...
  print('electronics'.capitalized); // Electronics
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `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 localmente no DartPad ou via `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.

: Extensão de num (Formatação de Valores)

DART
extension NumFormatting on num {
  String toUSD() => '\$${toStringAsFixed(2)} USD';
  String toCompact() {
    if (this >= 1000000) return '\$${(this / 1000000).toStringAsFixed(1)}M USD';
    if (this >= 1000) return '\$${(this / 1000).toStringAsFixed(1)}K USD';
    return toUSD();
  }

  double get asK => this / 1000;
  double get asM => this / 1000000;

  bool isBetween(num from, num to) => from <= this && this <= to;
}

void main() {
  print(1500.0.toUSD());       // $1500.00 USD
  print(1500000.0.toCompact()); // $1.5M USD
  print(5000.asK);             // 5.0
  print(1500.0.isBetween(1000, 2000)); // true
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `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 localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.

: Extensão de List

DART
extension ListStats on List``<double>`` {
  double get sum => fold(0, (a, b) => a + b);
  double get average => isEmpty ? 0 : sum / length;
  double get median {
    final sorted = [...this]..sort();
    final mid = length ~/ 2;
    return length.isEven
        ? (sorted[mid - 1] + sorted[mid]) / 2
        : sorted[mid];
  }
}

void main() {
  final amounts = [1500.0, 3200.0, 890.0, 50.0];
  print('Sum: ${amounts.sum.toUSD()}');       // Sum: $5640.00 USD
  print('Average: ${amounts.average.toUSD()}'); // Average: $1410.00 USD
  print('Median: ${amounts.median.toUSD()}');  // Median: $1195.00 USD
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.

6. Extensões, Privacidade e Conflitos de Nomenclatura

(1) Resolução de Conflitos de Nomenclatura

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.

: Resolução de Conflito via Namespaces

DART
extension MathExtras on num {
  int get squared => (this * this).toInt();
}

extension StringExtras on String {
  String get reversed => split('').reversed.join('');
}

// Se duas extensões têm o mesmo nome de método
extension DoubleExtras on double {
  String toMoney() => '\$${toStringAsFixed(2)}';
}

extension IntExtras on int {
  String toMoney() => '\$${this}.00';
}

void main() {
  // Chamada direta - compilador resolve por tipo
  print(5.squared);            // 25
  print('hello'.reversed);     // olleh

  // Resolução explícita quando ambíguo
  print(DoubleExtras(1500.5).toMoney());  // $1500.50
  print(IntExtras(1500).toMoney());       // $1500.00
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.
Cenário de Conflito Resolução
Duas extensões definem o mesmo nome de método Chamar explicitamente via ExtensionName(obj).method()
Método de extensão e método da classe com mesmo nome Método da classe tem prioridade, método de extensão é oculto
Duas extensões em arquivos diferentes Prioridade das extensões importadas depende da ordem de import

7. Cenário do Bob: Enum OrderStatus + Extensão de String

▶ Exemplo

TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.

: Enum e Extensão práticos para DataPipeline

DART
// Enum de status do pedido com lógica de negócio
enum OrderStatus {
  pending(label: 'Awaiting Processing', isFinal: false),
  processing(label: 'Being Processed', isFinal: false),
  shipped(label: 'In Transit', isFinal: false),
  delivered(label: 'Completed', isFinal: true),
  cancelled(label: 'Cancelled', isFinal: true),
  refunded(label: 'Refunded', isFinal: true);

  final String label;
  final bool isFinal;

  const OrderStatus({required this.label, required this.isFinal});

  bool get isActive => !isFinal;
  bool get canCancel => this == pending || this == processing;
  bool get canRefund => this == delivered;
}

// Extensão de String para formatação do DataPipeline
extension DataPipelineString on String {
  String get asOrderId => 'ORD-$this';
  String toUSD() => '\$$this USD';
  String toCategoryLabel => split('_').map((w) => w.capitalizeFirst).join(' ');
}

extension StringCap on String {
  String get capitalizeFirst =>
      isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';
}

// Extensão de num para formatação de receita
extension RevenueFormatting on num {
  String toRevenue() => '\$${toStringAsFixed(2)} USD';
  String toCompactRevenue() {
    if (this >= 1000000) return '\$${(this / 1000000).toStringAsFixed(1)}M USD';
    if (this >= 1000) return '\$${(this / 1000).toStringAsFixed(1)}K USD';
    return toRevenue();
  }
}

void main() {
  // Uso do enum
  final status = OrderStatus.shipped;
  print('Status: ${status.label}');      // In Transit
  print('Active: ${status.isActive}');   // true
  print('Can cancel: ${status.canCancel}'); // false

  // Extensões de String
  print('001'.asOrderId);               // ORD-001
  print('1500.00'.toUSD());             // $1500.00 USD

  // Formatação de receita
  print(1500000.toCompactRevenue());     // $1.5M USD
  print(52500.75.toRevenue());           // $52500.75 USD

  // Switch exaustivo no enum
  for (final s in OrderStatus.values) {
    final action = switch (s) {
      OrderStatus.pending => 'Queue for processing',
      OrderStatus.processing => 'Monitor progress',
      OrderStatus.shipped => 'Track delivery',
      OrderStatus.delivered => 'Send confirmation',
      OrderStatus.cancelled => 'Process cancellation',
      OrderStatus.refunded => 'Update records',
    };
    print('  ${s.name}: $action');
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.

8. Exemplo Completo: Máquina de Estados de Pedidos do DataPipeline

DART
// ============================================
// DataPipeline Order State Machine
// Enhanced enums + extensões em ação
// ============================================

enum OrderStatus {
  pending(label: 'Awaiting Processing', isFinal: false, color: 'yellow'),
  processing(label: 'Being Processed', isFinal: false, color: 'blue'),
  shipped(label: 'In Transit', isFinal: false, color: 'orange'),
  delivered(label: 'Completed', isFinal: true, color: 'green'),
  cancelled(label: 'Cancelled', isFinal: true, color: 'red'),
  refunded(label: 'Refunded', isFinal: true, color: 'gray');

  final String label;
  final bool isFinal;
  final String color;

  const OrderStatus({
    required this.label,
    required this.isFinal,
    required this.color,
  });

  bool get isActive => !isFinal;
  bool get canTransition => !isFinal;

  List``<OrderStatus>`` get allowedTransitions => switch (this) {
    pending => [processing, cancelled],
    processing => [shipped, cancelled],
    shipped => [delivered],
    delivered => [refunded],
    cancelled => [],
    refunded => [],
  };

  bool canTransitionTo(OrderStatus target) =>
      allowedTransitions.contains(target);
}

extension NumRevenue on num {
  String toUSD() => '\$${toStringAsFixed(2)} USD';
}

class Order {
  final String id;
  final double amount;
  OrderStatus status;

  Order({required this.id, required this.amount, this.status = OrderStatus.pending});

  bool transitionTo(OrderStatus newStatus) {
    if (!status.canTransitionTo(newStatus)) {
      print('  Cannot transition from ${status.name} to ${newStatus.name}');
      return false;
    }
    print('  $id: ${status.name} → ${newStatus.name}');
    status = newStatus;
    return true;
  }

  String get summary => '$id: ${status.label} (${amount.toUSD()})';
}

void main() {
  final order = Order(id: 'ORD-001', amount: 1500.0);

  print('=== Order State Machine ===');
  print('Initial: ${order.summary}');

  // Transições válidas
  order.transitionTo(OrderStatus.processing);  // OK
  order.transitionTo(OrderStatus.shipped);      // OK
  order.transitionTo(OrderStatus.delivered);    // OK

  // Transição inválida
  order.transitionTo(OrderStatus.cancelled);  // Cannot: delivered → cancelled

  // Reembolso válido
  order.transitionTo(OrderStatus.refunded);   // OK

  print('\nFinal: ${order.summary}');

  // Imprimir todos os estados e transições
  print('\n=== State Transition Table ===');
  for (final status in OrderStatus.values) {
    final targets = status.allowedTransitions.map((t) => t.name).join(', ');
    print('  ${status.name.padRight(12)} → ${targets.isEmpty ? '(final)' : targets}');
  }

  // Estatísticas de status
  print('\n=== Status Properties ===');
  final activeCount = OrderStatus.values.where((s) => s.isActive).length;
  final finalCount = OrderStatus.values.where((s) => s.isFinal).length;
  print('Active states: $activeCount');
  print('Final states:  $finalCount');
  print('Total states:  ${OrderStatus.values.length}');
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad ou via `dart run`. Todos os exemplos do curso Dart são baseado em Dart 3.x / Flutter 3.x. Os resultados podem variar ligeiramente dependendo da versão do SDK.

Saída:

TEXT 📖 Somente leitura
=== Order State Machine ===
Initial: ORD-001: Awaiting Processing ($1500.00 USD)
  ORD-001: pending → processing
  ORD-001: processing → shipped
  ORD-001: shipped → delivered
  Cannot transition from delivered to cancelled
  ORD-001: delivered → refunded

Final: ORD-001: Refunded ($1500.00 USD)

=== State Transition Table ===
  pending      → processing, cancelled
  processing   → shipped, cancelled
  shipped      → delivered
  delivered    → refunded
  cancelled    → (final)
  refunded     → (final)

=== Status Properties ===
Active states: 3
Final states:  3
Total states:  6

❓ Perguntas Frequentes

P: Qual a diferença entre um Enhanced Enum e um enum regular? R: Enhanced Enums podem ter propriedades, construtores e métodos. Enums regulares possuem apenas name e index. Dart 2.17+ recomenda usar Enhanced Enums em todos os lugares.

P: Enums podem implementar interfaces? R: Sim. Enums podem implementar interfaces, por exemplo, `enum Status implements Comparable`````. No entanto, eles não podem estender outras classes (enums herdam implicitamente de Enum).

P: Métodos de extensão podem acessar membros privados? R: Não. Métodos de extensão são definidos fora da classe e só podem acessar membros públicos. Esta é a diferença fundamental entre extensões e métodos de classe.

P: Métodos de extensão são despachados estática ou dinamicamente? R: Estaticamente. O compilador determina qual método de extensão chamar com base no tipo declarado da variável em tempo de compilação. O tipo em runtime não importa. Esta é a diferença essencial dos métodos de classe, que são despachados dinamicamente.

P: Extensões podem adicionar propriedades? R: Elas podem adicionar propriedades computadas (getters) mas não podem adicionar variáveis de instância (propriedades armazenadas). Extensões não modificam o layout de memória de um objeto.

P: O que acontece se duas extensões definirem um método com o mesmo nome? R: Se o compilador puder distinguir pelo tipo do receptor, ele seleciona automaticamente o correto. Se não puder distinguir (ambíguo), você deve especificar explicitamente usando ExtensionName(obj).method().

P: Há diferença de desempenho entre values e byName para enums? R: values retorna uma lista em cache, O(1). byName itera sobre values para encontrar uma correspondência, O(n). Para buscas frequentes, considere criar seu próprio cache Map.


📖 Resumo


📝 Exercícios

  1. Básico (Dificuldade ⭐): Defina um Enhanced Enum OutputFormat contendo os valores json, csv e html, cada um com uma propriedade fileExtension (ex: .json) e uma propriedade mimeType (ex: application/json).
  2. Intermediário (Dificuldade ⭐⭐): Adicione métodos de extensão a String: toOrderId (formata como ORD-XXX), isValidEmail (valida formato de email), truncateWithEllipsis(int max) (trunca e adiciona reticências), e teste-os.
  3. Desafio (Dificuldade ⭐⭐⭐): Use um Enhanced Enum para implementar uma máquina de estados de workflow completa (Draft → Review → Approved → Published). Cada estado deve definir seus alvos de transição permitidos. Implemente um método transitionTo() e verifique que transições ilegais são rejeitadas.

← 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%