Dart: Novos Recursos do Dart 3

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

Os três grandes recursos do Dart 3 — eleve seu código de "funciona" para "elegante."

1. O que Você Aprenderá


2. A História Real de um Desenvolvedor

(1) Dor: Hierarquia de Tipos Profunda e if-else Verboso

O DataPipeline do Bob tem 3 fontes de dados (API/Arquivo/Banco de dados), cada uma com configurações e métodos de conexão diferentes. Ele as implementou usando herança, mas a hierarquia era profunda e o código verboso. Ao analisar respostas de API, um grande número de verificações if-else era necessário para códigos de status e estruturas de resposta, com 200 linhas de código condicional escritas para apenas 5 tipos de resposta.

(2) Solução com Novos Recursos do Dart 3

Sealed Classes restringem os tipos de fonte de dados, Pattern Matching trata elegantemente várias respostas, e Records fornecem múltiplos valores de retorno leves.

DART
// Classe sealed: tipos de fonte de dados exaustivos
sealed class DataSource {}
class ApiSource extends DataSource { final String endpoint; ApiSource(this.endpoint); }
class FileSource extends DataSource { final String path; FileSource(this.path); }

// Pattern matching em classe sealed - compilador garante que todos os casos são tratados
String describe(DataSource source) => switch (source) {
  ApiSource(:final endpoint) => 'API: $endpoint',
  FileSource(:final path) => 'Arquivo: $path',
};

// Records: retornar múltiplos valores
(String, double) parseResponse(String raw) => ('ORD-001', 1500.0);
TEXT 📖 Somente leitura
> **Saída:** Execute no DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados no Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.

(3) Benefícios


3. Records

(1) Fundamentos de Record

▶ Exemplo

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

: Criando e Usando Records

DART
void main() {
  // Record posicional
  var order = ('ORD-001', 1500.0, 'completed');
  print(order.$1);  // ORD-001
  print(order.$2);  // 1500.0
  print(order.$3);  // completed

  // Record nomeado (preferido para legibilidade)
  var order2 = (id: 'ORD-002', amount: 3200.0, status: 'pending');
  print(order2.id);      // ORD-002
  print(order2.amount);  // 3200.0
  print(order2.status);  // pending

  // Mistura posicional e nomeado
  var mixed = ('ORD-003', 890.0, category: 'Electronics');
  print(mixed.$1);       // ORD-003
  print(mixed.category); // Electronics
}
TEXT 📖 Somente leitura
> **Saída:** Execute no DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados no Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.

▶ Exemplo

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

: Records como Valores de Retorno de Função

DART
// Múltiplos valores de retorno com records
({String id, double amount, double tax}) calculateOrder(double amount, double taxRate) {
  return (
    id: 'ORD-${DateTime.now().millisecondsSinceEpoch}',
    amount: amount,
    tax: amount * taxRate,
  );
}

// Record posicional para agrupamento rápido
(String, double) parseAmount(String input) {
  final parts = input.split(':');
  return (parts[0], double.parse(parts[1]));
}

void main() {
  final order = calculateOrder(1500.0, 0.08);
  print('ID: ${order.id}, Valor: \$${order.amount}, Imposto: \$${order.tax}');

  final (label, value) = parseAmount('Revenue:52500.75');
  print('$label: \$$value USD');
}
TEXT 📖 Somente leitura
> **Saída:** Execute no DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados no Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
Recurso de Record Sintaxe Descrição
Campos posicionais .$1, .$2 Acesso por posição
Campos nomeados .name Acesso por nome
Anotação de tipo (String, int) ou ({String name, int count}) Tipo explícito
Igualdade Igualdade por valor Iguais se os valores dos campos forem os mesmos

4. Pattern Matching

(1) Tipos de Pattern Matching

▶ Exemplo

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

: Padrões de Variáveis e Desconstrução

DART
void main() {
  // Padrão de variável - extrair valores
  var (id, amount, status) = ('ORD-001', 1500.0, 'completed');
  print('ID: $id, Valor: $amount, Status: $status');

  // Desconstrução de record com campos nomeados
  var (:id, :amount, :status) = (id: 'ORD-002', amount: 3200.0, status: 'pending');
  print('ID: $id, Valor: $amount, Status: $status');

  // Desconstrução de lista
  var [first, second, ...rest] = [1, 2, 3, 4, 5];
  print('Primeiro: $first, Segundo: $second, Resto: $rest');

  // Desconstrução de mapa
  var {'id': orderId, 'amount': orderAmount} = {'id': 'ORD-003', 'amount': 890.0};
  print('Pedido: $orderId, Valor: $orderAmount');
}
TEXT 📖 Somente leitura
> **Saída:** Execute no DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados no Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.

▶ Exemplo

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

: Padrão if-case

DART
void main() {
  Object value = '1,500.00 USD';

  // Pattern matching if-case
  if (value case String s when s.contains('USD')) {
    print('Valor em USD: $s');
  }

  // Pattern matching em record
  var response = (statusCode: 200, body: '{"orders": 1500}');
  if (response case (statusCode: 200, :var body)) {
    print('Resposta de sucesso: $body');
  }

  // Pattern matching em lista
  var orders = ['ORD-001', 'ORD-002', 'ORD-003'];
  if (orders case [var first, var second, ...]) {
    print('Primeiros dois: $first, $second');
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute no DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados no Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.

▶ Exemplo

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

: Padrão switch

DART
String classifyOrder(Object value) => switch (value) {
  // Padrão de tipo
  int i when i > 1000 => 'Inteiro de alto valor: $i',
  int i => 'Inteiro de baixo valor: $i',
  double d when d >= 1000 => 'Premium: \$${d.toStringAsFixed(2)}',
  double d => 'Padrão: \$${d.toStringAsFixed(2)}',
  String s => 'String: $s',
  List l when l.length > 100 => 'Lote grande: ${l.length} itens',
  List l => 'Lote pequeno: ${l.length} itens',
  _ => 'Tipo desconhecido',
};

// Pattern matching com records
String describeResponse((int, String) response) => switch (response) {
  (200, var body) => 'OK: $body',
  (404, _) => 'Não Encontrado',
  (500, var msg) => 'Erro no Servidor: $msg',
  (>= 400, var msg) => 'Erro do Cliente: $msg',
  _ => 'Resposta desconhecida',
};

void main() {
  print(classifyOrder(1500));          // Inteiro de alto valor: 1500
  print(classifyOrder(890.0));         // Padrão: $890.00
  print(classifyOrder([1, 2, 3]));     // Lote pequeno: 3 itens

  print(describeResponse((200, 'OK')));      // OK: OK
  print(describeResponse((404, 'Missing')));  // Não Encontrado
  print(describeResponse((500, 'Crash')));    // Erro no Servidor: Crash
}
TEXT 📖 Somente leitura
> **Saída:** Execute no DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados no Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
Tipo de Padrão Sintaxe Uso
Padrão de variável var x Extrair valores

5. Sealed Classes

(1) Hierarquia de Classes Sealed

100%
classDiagram
  class DataSource {
    <<sealed>>
  }
  class ApiSource {
    +String endpoint
    +Map headers
  }
  class FileSource {
    +String path
    +Encoding encoding
  }
  class DatabaseSource {
    +String connectionString
  }
  DataSource <|-- ApiSource
  DataSource <|-- FileSource
  DataSource <|-- DatabaseSource
  note for DataSource "switch exaustivo garantido"
TEXT 📖 Somente leitura
> **Saída:** Execute no DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados no Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.

▶ Exemplo

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

: Definição de Classe Sealed e Correspondência Exaustiva

DART
sealed class DataSource {
  const DataSource();
}

class ApiSource extends DataSource {
  final String endpoint;
  final Map<String, String> headers;

  const ApiSource(this.endpoint, {this.headers = const {}});
}

class FileSource extends DataSource {
  final String path;

  const FileSource(this.path);
}

class DatabaseSource extends DataSource {
  final String connectionString;

  const DatabaseSource(this.connectionString);
}

// Switch exaustivo - compilador verifica todos os subtipos
String describeSource(DataSource source) => switch (source) {
  ApiSource(:final endpoint, :final headers) =>
      'API: $endpoint (${headers.length} headers)',
  FileSource(:final path) =>
      'Arquivo: $path',
  DatabaseSource(:final connectionString) =>
      'Banco de dados: $connectionString',
};

// Se você adicionar um novo subtipo e esquecer de atualizar o switch,
// o compilador reportará um erro!
void main() {
  final sources = <DataSource>[
    ApiSource('https://api.example.com/orders', headers: {'Authorization': 'Bearer token'}),
    FileSource('/data/orders.csv'),
    DatabaseSource('postgresql://localhost:5432/orders'),
  ];

  for (final source in sources) {
    print(describeSource(source));
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute no DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados no Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.
Recurso de Classe Sealed Descrição
Restrição de subclasses Devem ser definidas na mesma biblioteca
Verificação exaustiva switch deve cobrir todos os subtipos
Garantia em tempo de compilação Switch omitido causará erro de compilação quando nova subclasse for adicionada
Não pode ser instanciada É implicitamente abstrata

6. O Trio em Ação

▶ Exemplo

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

: Combinação Sealed + Pattern + Records

DART
// Classe sealed para resultados de API
sealed class ApiResult<T> {
  const ApiResult();
}

class Success<T> extends ApiResult<T> {
  final T data;
  final (int, String) metadata;  // Record: (statusCode, message)

  const Success(this.data, this.metadata);
}

class ApiError<T> extends ApiResult<T> {
  final String message;
  final int? statusCode;

  const ApiError(this.message, {this.statusCode});
}

class NetworkError<T> extends ApiResult<T> {
  final String reason;

  const NetworkError(this.reason);
}

// Pattern matching com desconstrução
String handleResult(ApiResult<List<String>> result) => switch (result) {
  Success(:final data, metadata: (200, final msg)) =>
      'OK ($msg): ${data.length} itens carregados',
  Success(:final data, metadata: (final code, _)) =>
      'Carregado com status $code: ${data.length} itens',
  ApiError(:final message, statusCode: final code?) =>
      'Erro de API [$code]: $message',
  ApiError(:final message) =>
      'Erro de API: $message',
  NetworkError(:final reason) =>
      'Erro de Rede: $reason',
};

void main() {
  final results = <ApiResult<List<String>>>[
    Success(['ORD-001', 'ORD-002'], (200, 'OK')),
    Success(['ORD-003'], (206, 'Partial Content')),
    ApiError('Rate limit exceeded', statusCode: 429),
    ApiError('Erro desconhecido'),
    NetworkError('Tempo limite de conexão esgotado'),
  ];

  for (final result in results) {
    print(handleResult(result));
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute no DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados no Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.

7. Cenário do Bob: Fontes de Dados e Análise de Resultados no DataPipeline

▶ Exemplo

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

: Abstração Completa de Fonte de Dados

DART
sealed class DataSource {
  const DataSource();

  String get displayName;
}

class ApiSource extends DataSource {
  final String endpoint;
  final Duration timeout;

  const ApiSource(this.endpoint, {this.timeout = const Duration(seconds: 10)});

  @override
  String get displayName => 'API ($endpoint)';
}

class FileSource extends DataSource {
  final String path;
  final String format;

  const FileSource(this.path, {this.format = 'csv'});

  @override
  String get displayName => 'Arquivo ($path, $format)';
}

class DatabaseSource extends DataSource {
  final String connectionString;
  final String query;

  const DatabaseSource(this.connectionString, {this.query = 'SELECT * FROM orders'});

  @override
  String get displayName => 'Banco de dados ($connectionString)';
}

// Resultado do processamento com Records
typedef ProcessingResult = ({int processed, int skipped, double revenue, Duration time});

ProcessingResult processDataSource(DataSource source) => switch (source) {
  ApiSource(:final endpoint, :final timeout) => (
    processed: 50000,
    skipped: 120,
    revenue: 525000.0,
    time: timeout,
  ),
  FileSource(:final path, :final format) => (
    processed: 100000,
    skipped: 350,
    revenue: 1200000.0,
    time: Duration(seconds: 5),
  ),
  DatabaseSource(:final connectionString, :final query) => (
    processed: 1200000,
    skipped: 800,
    revenue: 15000000.0,
    time: Duration(seconds: 15),
  ),
};

void main() {
  final sources = <DataSource>[
    ApiSource('https://api.example.com/orders'),
    FileSource('/data/orders.csv'),
    DatabaseSource('postgresql://localhost:5432/orders'),
  ];

  print('=== Análise de Fontes do DataPipeline ===');
  for (final source in sources) {
    final (:processed, :skipped, :revenue, :time) = processDataSource(source);
    print('\n${source.displayName}:');
    print('  Processados: ${processed} pedidos');
    print('  Ignorados:   $skipped registros');
    print('  Receita:     \$${revenue.toStringAsFixed(2)} USD');
    print('  Tempo:       ${time.inSeconds}s');
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute no DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados no Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.

8. Exemplo Completo: Sistema de Processamento de Respostas do DataPipeline

DART
// ============================================
// Sistema de Processamento de Respostas do DataPipeline
// Classes Sealed + Pattern matching + Records
// ============================================

// Classe sealed para diferentes tipos de resposta
sealed class ApiResponse {
  const ApiResponse();
}

class SuccessResponse extends ApiResponse {
  final int statusCode;
  final Map<String, dynamic> data;

  const SuccessResponse(this.statusCode, this.data);
}

class ErrorResponse extends ApiResponse {
  final int statusCode;
  final String message;
  final String? details;

  const ErrorResponse(this.statusCode, this.message, {this.details});
}

class TimeoutResponse extends ApiResponse {
  final Duration timeout;
  final String endpoint;

  const TimeoutResponse(this.timeout, this.endpoint);
}

class RedirectResponse extends ApiResponse {
  final String newEndpoint;
  final int statusCode;

  const RedirectResponse(this.newEndpoint, this.statusCode);
}

// Resultado do processamento como um Record
typedef Outcome = ({bool success, String message, double? revenue});

// Pattern matching em todos os tipos de resposta - exaustivo!
Outcome handleResponse(ApiResponse response) => switch (response) {
  SuccessResponse(statusCode: 200, :final data) when data.containsKey('orders') => (
    success: true,
    message: 'Carregados ${data['orders']} pedidos',
    revenue: (data['revenue'] as num?)?.toDouble(),
  ),
  SuccessResponse(statusCode: 200, :final data) => (
    success: true,
    message: 'OK mas sem campo orders: ${data.keys}',
    revenue: null,
  ),
  SuccessResponse(statusCode: 206, :final data) => (
    success: true,
    message: 'Dados parciais: ${data.length} campos',
    revenue: null,
  ),
  SuccessResponse(statusCode: final code) => (
    success: true,
    message: 'Código de sucesso inesperado: $code',
    revenue: null,
  ),
  ErrorResponse(statusCode: 429, :final message) => (
    success: false,
    message: 'Limite de taxa atingido: $message',
    revenue: null,
  ),
  ErrorResponse(statusCode: final code, :final message, :final details?) => (
    success: false,
    message: 'Erro [$code]: $message - $details',
    revenue: null,
  ),
  ErrorResponse(statusCode: final code, :final message) => (
    success: false,
    message: 'Erro [$code]: $message',
    revenue: null,
  ),
  TimeoutResponse(:final timeout, :final endpoint) => (
    success: false,
    message: 'Timeout após ${timeout.inSeconds}s em $endpoint',
    revenue: null,
  ),
  RedirectResponse(:final newEndpoint, :final statusCode) => (
    success: false,
    message: 'Redireção ($statusCode) para $newEndpoint',
    revenue: null,
  ),
};

void main() {
  final responses = <ApiResponse>[
    SuccessResponse(200, {'orders': 50000, 'revenue': 525000.0}),
    SuccessResponse(200, {'products': 3000}),
    SuccessResponse(206, {'partial': true}),
    ErrorResponse(429, 'Limite de taxa excedido'),
    ErrorResponse(500, 'Erro interno do servidor', details: 'Conexão com banco de dados perdida'),
    ErrorResponse(404, 'Não encontrado'),
    TimeoutResponse(const Duration(seconds: 30), '/api/v1/orders'),
    RedirectResponse('/api/v2/orders', 301),
  ];

  print('=== Gerenciador de Respostas do DataPipeline ===\n');
  for (final response in responses) {
    final (:success, :message, :revenue) = handleResponse(response);
    final status = success ? 'OK' : 'FALHA';
    final rev = revenue != null ? ' (\$${revenue.toStringAsFixed(2)} USD)' : '';
    print('[$status] $message$rev');
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute no DartPad local ou com `dart run`. Todos os exemplos do curso Dart são baseados no Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo da versão do SDK.

Saída:

TEXT 📖 Somente leitura
=== Gerenciador de Respostas do DataPipeline ===

[OK] Carregados 50000 pedidos ($525000.00 USD)
[OK] OK mas sem campo orders: (products)
[OK] Dados parciais: 1 campos
[FALHA] Limite de taxa atingido: Limite de taxa excedido
[FALHA] Erro [500]: Erro interno do servidor - Conexão com banco de dados perdida
[FALHA] Erro [404]: Não encontrado
[FALHA] Timeout após 30s em /api/v1/orders
[FALHA] Redireção (301) para /api/v2/orders

❓ Perguntas Frequentes

P: Qual é a diferença entre Records e Classes? R: Records são estruturas de dados anônimas, baseadas em valor e leves; Classes têm nomes, são baseadas em referência e podem ter métodos. Records são adequados para passagem simples de dados, enquanto Classes são para lógica de negócio complexa.

P: Qual é a diferença entre uma classe Sealed e uma classe abstrata? R: Classes Sealed restringem subclasses à mesma biblioteca, permitindo que o compilador realize verificações exaustivas; classes abstratas podem ter subclasses definidas em qualquer lugar, impossibilitando exaustividade. Use sealed quando precisar de garantias exaustivas.

P: Onde Pattern matching pode ser usado? R: Expressões switch, instruções switch, if-case, declarações de variáveis, loops for-in. No Dart 3, padrões estão em todo lugar.

P: Qual é o desempenho de Records? R: Records são compilados para objetos comuns, com desempenho comparável a pequenas classes. Não há overhead extra, mas também não são mais rápidos que classes. Escolha baseado em legibilidade.

P: Uma classe sealed pode ter construtor? R: Pode ter construtores factory (que retornam subclasses), mas a classe em si não pode ser instanciada (é implicitamente abstrata). Construtores são principalmente para compartilhar lógica de inicialização entre subclasses.

P: Existe diferença entre _ e default em uma expressão switch? R: Funcionalmente, são os mesmos — ambos são correspondências curinga (catch-all). Dart 3 recomenda usar _ (padrão curinga) para concisão. default é a sintaxe legada mantida para compatibilidade.

P: Records podem ser usados como chaves de um Map? R: Sim. Records são baseados em igualdade de valor e implementam automaticamente == e hashCode. (1, 2) == (1, 2) é true.


📖 Resumo


📝 Exercícios

  1. Básico (Dificuldade ⭐): Defina dois Records: um record posicional (String, double) representando ID e valor do pedido, e um record nomeado ({String id, double amount, String status}). Crie instâncias e desconstrua/imprima-as.
  2. Intermediário (Dificuldade ⭐⭐): Use uma Classe Sealed para definir 3 métodos de pagamento (CreditCard/PayPal/BankTransfer), cada um com campos diferentes. Implemente um método describePayment() usando uma expressão switch, garantindo exaustividade.
  3. Desafio (Dificuldade ⭐⭐⭐): Projete um sistema completo de processamento de respostas de API: Use uma Classe Sealed para definir tipos de resposta (Success/ValidationError/ServerError/Timeout), cada um carregando dados Record diferentes. Implemente um gerenciador de respostas usando Pattern Matching, incorporando padrões relacionais e condições de guarda.

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