Dart: Dart Stream

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

Stream é um rio de dados — contínuo, processado à medida que chega, sem necessidade de esperar que tudo chegue de uma vez.

1. O que Você Aprenderá


2. A História Real de um Desenvolvedor

(1) Dor: Milhões de Registros de Dados Não Podem Ser Carregados na Memória de Uma Só Vez

O DataPipeline do Bob inicialmente carregava todos os 1.200.000 pedidos na memória para processamento ao lidar com pedidos de e-commerce, resultando em um pico de memória de 4GB e causando o crash do servidor com erro de OOM três vezes. Além disso, conforme pedidos em tempo real continuavam a chegar, o modelo de processamento em lote era incapaz de lidar com os dados recém-chegados.

(2) Solução com Stream

Stream permite que os dados sejam processados um a um como um fluxo de água: processe um assim que chegar, mantendo o uso de memória estável em 50MB. Operadores de Stream (where/map/reduce) permitem definir declarativamente o pipeline de processamento.

DART
orderStream
  .where((order) => order.status == 'completed')
  .map((order) => order.amount)
  .fold(0, (sum, amount) => sum + amount)
  .then((total) => print('Receita: \$$total USD'));
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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. Fundamentos de Stream

(1) Subscrição Única vs Broadcast

100%
flowchart LR
  A[Fonte de Dados] --> B["StreamController"]
  B --> C["where() Filtrar"]
  C --> D["map() Transformar"]
  D --> E["expand() Achatar"]
  E --> F["take() Limitar"]
  F --> G["listen() Consumir"]
  subgraph Pipeline de Stream
    C
    D
    E
    F
  end
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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 Inscritos Reproduzível Caso de Uso
Stream de subscrição única 1 Não reproduzível Leitura de arquivos, respostas HTTP
Stream broadcast Múltiplos Não reproduzível Eventos de UI, dados em tempo real

▶ Exemplo

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

:Fundamentos de Stream

DART
import 'dart:async';

void main() async {
  // Criar stream a partir de iterável
  final stream = Stream.fromIterable([1, 2, 3, 4, 5]);

  // Escutar stream
  await for (final value in stream) {
    print('Recebido: $value');
  }

  // Alternativa: escutar com callback
  final stream2 = Stream.fromIterable(['ORD-001', 'ORD-002', 'ORD-003']);
  stream2.listen(
    (order) => print('Pedido: $order'),
    onDone: () => print('Stream concluída'),
    onError: (e) => print('Erro: $e'),
  );

  // Aguardar stream completar
  await stream2.drain();
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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.

4. Criação de Stream

▶ Exemplo

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

:Criação com fromIterable

DART
import 'dart:async';

void main() async {
  // A partir de iterável - emite cada elemento
  final orders = Stream.fromIterable([
    {'id': 'ORD-001', 'amount': 1500.0},
    {'id': 'ORD-002', 'amount': 3200.0},
    {'id': 'ORD-003', 'amount': 890.0},
  ]);

  await for (final order in orders) {
    print('Pedido: ${order['id']} - \$${order['amount']} USD');
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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 localmente no DartPad 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.

:Criação com periodic

DART
import 'dart:async';

void main() async {
  // Stream periódica - emite valores em intervalos regulares
  final ticker = Stream.periodic(
    const Duration(seconds: 1),
    (count) => 'Tick ${count + 1}',
  );

  // Pegar apenas os primeiros 5 ticks
  await for (final tick in ticker.take(5)) {
    print(tick);
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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 localmente no DartPad 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.

:Criação com StreamController

DART
import 'dart:async';

void main() async {
  final controller = StreamController<String>();

  // Adicionar dados ao stream
  controller.add('ORD-001');
  controller.add('ORD-002');
  controller.add('ORD-003');

  // Fechar o stream quando terminar
  controller.close();

  // Consumir o stream
  await for (final order in controller.stream) {
    print('Processando: $order');
  }
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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.
Método de Criação Sintaxe Fonte de Dados Caso de Uso
fromIterable Stream.fromIterable(list) Coleção existente Testes, conjuntos pequenos
periodic Stream.periodic(duration, fn) Geração temporizada Timers, polling
StreamController StreamController<T>() Adicionado manualmente Fontes de eventos, dados em tempo real
empty Stream.empty() Nenhum Stream vazia
value Stream.value(v) Valor único Envolver um único valor

5. Operadores de Stream

▶ Exemplo

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

:where — Filtragem

DART
import 'dart:async';

void main() async {
  final amounts = Stream.fromIterable([1500.0, 50.0, 3200.0, 890.0, -100.0]);

  // Filtrar apenas valores positivos >= 100
  final highValue = amounts.where((a) => a >= 100);

  await for (final amount in highValue) {
    print('\$${amount.toStringAsFixed(2)} USD');
  }
  // Saída: $1500.00 USD, $3200.00 USD, $890.00 USD
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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 localmente no DartPad ou with `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.

:map — Transformação

DART
import 'dart:async';

void main() async {
  final amounts = Stream.fromIterable([1500.0, 3200.0, 890.0]);

  // Transformar valores em strings formatadas
  final formatted = amounts.map((a) => '\$${a.toStringAsFixed(2)} USD');

  await for (final text in formatted) {
    print(text);
  }
  // Saída: $1500.00 USD, $3200.00 USD, $890.00 USD
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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 localmente no DartPad 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.

:expand — Achatar

DART
import 'dart:async';

void main() async {
  final orders = Stream.fromIterable([
    {'id': 'ORD-001', 'items': ['Laptop', 'Mouse']},
    {'id': 'ORD-002', 'items': ['Keyboard']},
  ]);

  // Expand: cada pedido → múltiplos itens
  final items = orders.expand((order) =>
      (order['items'] as List).cast<String>());

  await for (final item in items) {
    print('Item: $item');
  }
  // Saída: Item: Laptop, Item: Mouse, Item: Keyboard
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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 localmente no DartPad 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.

:take / skip / distinct

DART
import 'dart:async';

void main() async {
  final data = Stream.fromIterable([1, 1, 2, 2, 3, 3, 4, 5]);

  // Pegar os primeiros 3 elementos
  print('take(3):');
  await data.take(3).forEach(print);  // 1, 1, 2

  // Pular os primeiros 2
  print('skip(2):');
  await Stream.fromIterable([1, 1, 2, 2, 3]).skip(2).forEach(print);  // 2, 2, 3

  // Remover duplicatas consecutivas
  print('distinct:');
  await Stream.fromIterable([1, 1, 2, 2, 3, 3]).distinct().forEach(print);  // 1, 2, 3
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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.
Operador Função Tipo de Retorno
where Filtrar elementos Stream<T>
map Transformar elementos Stream<R>
expand Achatar um-para-muitos Stream<T>
take Pegar primeiros N elementos Stream<T>
skip Pular primeiros N elementos Stream<T>
distinct Remover duplicatas consecutivas Stream<T>
takeWhile Pegar enquanto condição for verdadeira Stream<T>
skipWhile Pular enquanto condição for verdadeira Stream<T>

6. StreamController e StreamSubscription

(1) StreamController

▶ Exemplo

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

:Uso Completo do StreamController

DART
import 'dart:async';

class OrderStream {
  final _controller = StreamController<Map<String, dynamic>>();

  Stream<Map<String, dynamic>> get stream => _controller.stream;

  void addOrder(Map<String, dynamic> order) => _controller.add(order);

  void addError(Object error) => _controller.addError(error);

  void close() => _controller.close();
}

void main() async {
  final orderStream = OrderStream();

  // Inscrever-se no stream
  final subscription = orderStream.stream.listen(
    (order) => print('Processando: ${order['id']}'),
    onError: (e) => print('Erro: $e'),
    onDone: () => print('Stream fechada'),
  );

  // Adicionar pedidos
  orderStream.addOrder({'id': 'ORD-001', 'amount': 1500.0});
  orderStream.addOrder({'id': 'ORD-002', 'amount': 3200.0});
  orderStream.addOrder({'id': 'ORD-003', 'amount': 890.0});

  // Fechar o stream
  orderStream.close();

  // Aguardar stream terminar
  await subscription.asFuture();
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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.

(2) StreamController Broadcast

▶ Exemplo

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

:Modo Broadcast

DART
import 'dart:async';

void main() async {
  // Controller broadcast - múltiplos ouvintes
  final controller = StreamController<String>.broadcast();

  // Múltiplos inscritos
  final sub1 = controller.stream.listen((e) => print('Sub1: $e'));
  final sub2 = controller.stream.listen((e) => print('Sub2: $e'));

  // Ambos os inscritos recebem o mesmo evento
  controller.add('ORD-001');
  controller.add('ORD-002');

  controller.close();
  await Future.delayed(const Duration(milliseconds: 100));
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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: Processamento de Fluxo de Pedidos em Tempo Real

▶ Exemplo

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

:Pipeline de Stream Processando Pedidos

DART
import 'dart:async';

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

  Order({required this.id, required this.amount, required this.status, required this.category});

  @override
  String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status, $category)';
}

void main() async {
  // Simular stream de pedidos em tempo real
  final controller = StreamController<Order>();

  // Construir pipeline de processamento
  final revenueByCategory = <String, double>{};
  var totalOrders = 0;
  var completedOrders = 0;

  controller.stream
      .where((order) => order.amount > 0)       // Filtrar inválidos
      .where((order) => order.status == 'completed')  // Apenas concluídos
      .map((order) => order)                     // Poderia transformar aqui
      .listen(
        (order) {
          completedOrders++;
          totalOrders++;
          revenueByCategory.update(
            order.category,
            (v) => v + order.amount,
            ifAbsent: () => order.amount,
          );
          print('  Processado: ${order.id} - \$${order.amount.toStringAsFixed(2)} USD');
        },
        onDone: () {
          print('\n=== Processamento do Stream Concluído ===');
          print('Pedidos concluídos: $completedOrders');
          for (final entry in revenueByCategory.entries) {
            print('  ${entry.key}: \$${entry.value.toStringAsFixed(2)} USD');
          }
          final total = revenueByCategory.values.fold(0.0, (a, b) => a + b);
          print('Receita total: \$${total.toStringAsFixed(2)} USD');
        },
      );

  // Simular pedidos chegando
  controller.add(Order(id: 'ORD-001', amount: 1500.0, status: 'completed', category: 'Electronics'));
  controller.add(Order(id: 'ORD-002', amount: -50.0, status: 'completed', category: 'Books'));      // Inválido
  controller.add(Order(id: 'ORD-003', amount: 3200.0, status: 'pending', category: 'Electronics'));  // Não concluído
  controller.add(Order(id: 'ORD-004', amount: 890.0, status: 'completed', category: 'Clothing'));
  controller.add(Order(id: 'ORD-005', amount: 2100.0, status: 'completed', category: 'Electronics'));

  controller.close();
  await Future.delayed(const Duration(milliseconds: 100));
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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: Pipeline de Stream do DataPipeline

DART
// ============================================
// Pipeline de Processamento de Stream do DataPipeline
// Stream de pedidos em tempo real com operadores
// ============================================

import 'dart:async';

class Order {
  final String id;
  final double amount;
  final String status;
  final String category;
  final String region;

  const Order({
    required this.id,
    required this.amount,
    required this.status,
    required this.category,
    this.region = 'US',
  });

  double get taxAmount => amount * 0.08;
  double get totalWithTax => amount + taxAmount;

  @override
  String toString() =>
      'Order($id, \$${amount.toStringAsFixed(2)}, $status, $category, $region)';
}

class StreamPipeline {
  final _controller = StreamController<Order>();
  final Map<String, double> _revenue = {};
  final Map<String, int> _count = {};
  int _totalProcessed = 0;
  int _totalSkipped = 0;
  late StreamSubscription<Order> _subscription;

  Stream<Order> get stream => _controller.stream;

  StreamPipeline() {
    _subscription = _controller.stream
        .where((o) => o.amount > 0)
        .where((o) => o.status != 'cancelled')
        .distinct((o) => o.id)  // Deduplicar por ID
        .listen(
          _processOrder,
          onError: (e) => print('Erro no pipeline: $e'),
          onDone: _printSummary,
        );
  }

  void _processOrder(Order order) {
    _totalProcessed++;
    _revenue.update(
      order.category,
      (v) => v + order.totalWithTax,
      ifAbsent: () => order.totalWithTax,
    );
    _count.update(order.category, (v) => v + 1, ifAbsent: () => 1);
  }

  void addOrder(Order order) => _controller.add(order);

  void addError(Object error) => _controller.addError(error);

  Future<void> close() async {
    await _controller.close();
    await _subscription.asFuture();
  }

  void _printSummary() {
    final totalRevenue = _revenue.values.fold(0.0, (a, b) => a + b);
    final totalCount = _count.values.fold(0, (a, b) => a + b);

    print('\n=== Relatório de Stream do DataPipeline ===');
    print('Processados: $_totalProcessed pedidos');
    print('Ignorados:   $_totalSkipped registros');
    print('Receita:     \$${totalRevenue.toStringAsFixed(2)} USD');
    print('Pedidos:     $totalCount');

    print('\n--- Por Categoria ---');
    final sorted = _revenue.entries.toList()
      ..sort((a, b) => b.value.compareTo(a.value));
    for (final entry in sorted) {
      final count = _count[entry.key] ?? 0;
      final avg = entry.value / count;
      print('  ${entry.key}:');
      print('    Pedidos:  $count');
      print('    Receita:  \$${entry.value.toStringAsFixed(2)} USD');
      print('    Média:    \$${avg.toStringAsFixed(2)} USD');
    }
  }
}

void main() async {
  final pipeline = StreamPipeline();

  // Simular stream de pedidos em tempo real
  final orders = [
    Order(id: 'ORD-001', amount: 1500.0, status: 'completed', category: 'Electronics'),
    Order(id: 'ORD-002', amount: -50.0, status: 'completed', category: 'Books'),
    Order(id: 'ORD-003', amount: 3200.0, status: 'pending', category: 'Electronics'),
    Order(id: 'ORD-004', amount: 890.0, status: 'completed', category: 'Clothing'),
    Order(id: 'ORD-001', amount: 1500.0, status: 'completed', category: 'Electronics'),  // Duplicado
    Order(id: 'ORD-005', amount: 2100.0, status: 'completed', category: 'Electronics'),
    Order(id: 'ORD-006', amount: 500.0, status: 'cancelled', category: 'Books'),
    Order(id: 'ORD-007', amount: 120.0, status: 'completed', category: 'Books'),
  ];

  // Emitir pedidos um a um (simulando tempo real)
  for (final order in orders) {
    pipeline.addOrder(order);
  }

  await pipeline.close();
}
TEXT 📖 Somente leitura
> **Saída:** Execute localmente no DartPad 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
=== Relatório de Stream do DataPipeline ===
Processados: 5 pedidos
Ignorados:   0 registros
Receita:     $8154.00 USD
Pedidos:     5

--- Por Categoria ---
  Electronics:
    Pedidos:  3
    Receita:  $7236.00 USD
    Média:    $2412.00 USD
  Clothing:
    Pedidos:  1
    Receita:  $961.20 USD
    Média:    $961.20 USD
  Books:
    Pedidos:  1
    Receita:  $129.60 USD
    Média:    $129.60 USD

❓ Perguntas Frequentes

P: Qual é a diferença entre Stream e Future? R: Um Future representa um único resultado assíncrono (0 ou 1 valor), enquanto um Stream representa uma sequência de eventos assíncronos (0 a muitos valores). Um Stream é uma generalização de um Future — uma versão de múltiplos valores.

P: Um Stream de subscrição única pode ser escutado múltiplas vezes? R: Não. Um Stream de subscrição única só pode ser escutado uma vez; uma segunda escuta lançará um StateError. Use um Stream broadcast (via asBroadcastStream() ou StreamController.broadcast()) quando múltiplos ouvintes forem necessários.

P: Operadores de Stream são preguiçosos (lazy)? R: Sim. Operadores como map/where não executam imediatamente; eles só começam a processar quando escutados. Isso é chamado de "stream frio" (cold stream).

P: Como lidar com erros em um Stream? R: Existem três formas: o callback onError no listen, o operador handleError, ou envolver await for com try-catch. Usar o callback onError no listen é recomendado.

P: Um StreamController precisa ser fechado? R: Sim. Chame controller.close() para fechar o stream e notificar os ouvintes de que o stream terminou. Não fechá-lo fará com que os ouvintes aguardem indefinidamente.

P: Quais são as limitações de um Stream broadcast? R: Um Stream broadcast não suporta pausa/retomada, e se um ouvinte se inscrever após um evento ter sido emitido, ele perderá os eventos anteriores. É adequado para cenários de "inscrição em tempo real".

P: Um Stream suporta contrapressão (backpressure)? R: Um Stream de subscrição única naturalmente suporta contrapressão — quando o ouvinte (consumidor) processa lentamente, o produtor aguardará. Um Stream broadcast não suporta contrapressão.


📖 Resumo


📝 Exercícios

  1. Básico (Dificuldade ⭐): Use Stream.fromIterable para criar um stream contendo 10 valores, use where para filtrar aqueles maiores que 500, use map para convertê-los para formato USD, e use listen para imprimir os resultados.
  2. Intermediário (Dificuldade ⭐⭐): Use StreamController para simular um stream de pedidos em tempo real, adicionando 1 pedido por segundo durante 5 segundos. Construa um pipeline para filtrar pedidos concluídos, agregar receita por categoria e, por fim, gerar um relatório.
  3. Desafio (Dificuldade ⭐⭐⭐): Implemente uma função de "mesclar streams" que combine dois streams de pedidos em um (intercalados por tempo), deduplicando-os e depois processando-os. Dica: Use StreamGroup (do pacote async) ou mescle manualmente com StreamController.

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