StreamBuilder: o que é, princípio de funcionamento e aplicação no Flutter

Autor: IT Sectr Publicado: 2026-07-03 Tempo de leitura: 8 min

StreamBuilder é um widget Flutter que reconstrói automaticamente a interface ao receber novos dados de um fluxo assíncrono. Ao contrário do FutureBuilder, que trabalha com um único resultado, o StreamBuilder suporta atualizações contínuas da UI durante todo o ciclo de vida do Stream. De acordo com a documentação oficial do Flutter (2026), o StreamBuilder é usado em aplicações em tempo real: chats, feeds de notícias, monitoramento de sensores e tickers financeiros. É uma ferramenta chave da programação reativa, onde a UI reflete o estado dos dados sem chamadas manuais ao setState.

Pontos principais

  • StreamBuilder — um widget que aceita um Stream e um snapshot de dados para renderização reativa da UI
  • Snapshot contém connectionState, data e error, definindo o estado atual do fluxo
  • ConnectionState passa por quatro fases: none, waiting, active, done
  • AsyncSnapshot — um objeto imutável que garante a consistência dos dados em cada quadro
  • StreamController gerencia o fluxo: adiciona dados, trata erros e fecha o Stream

O que é StreamBuilder

StreamBuilder é um widget do pacote Flutter SDK que se inscreve em um Stream e reconstrói seu elemento filho a cada novo evento do fluxo. O StreamBuilder aceita um objeto Stream e retorna um widget baseado no snapshot mais recente recebido do fluxo.

Na arquitetura Flutter, o StreamBuilder pertence ao grupo de widgets Builder que separam a construção da UI do estado dos dados. Ao contrário do StatefulWidget, onde alterar o estado requer uma chamada explícita a setState, o StreamBuilder reage a eventos assíncronos automaticamente, simplificando o código e reduzindo o risco de erros de sincronização.

Ao contrário do FutureBuilder, que lida com um único valor assíncrono, o StreamBuilder é projetado para fluxos contínuos de dados. O FutureBuilder termina após receber o primeiro resultado, enquanto o StreamBuilder continua ouvindo o fluxo e atualizando a UI a cada novo evento.

O StreamBuilder é usado em todos os cenários onde os dados chegam continuamente: conexões WebSocket, callbacks de sensores, notificações do Firebase, filas de eventos Bluetooth e transmissão de estado da aplicação via BLoC. De acordo com a análise de projetos Flutter no GitHub (2025), o StreamBuilder está entre os três widgets Builder mais usados, junto com FutureBuilder e LayoutBuilder.

Conclusão: use StreamBuilder sempre que a UI precisar refletir dados que mudam continuamente, evitando o gerenciamento manual de estado via StatefulWidget.

Como o StreamBuilder funciona

StreamBuilder inscreve-se em um Stream no momento da construção e cancela a inscrição quando o widget é destruído. Cada vez que o Stream emite um evento, o StreamBuilder recebe um novo AsyncSnapshot e chama a função builder para reconstruir a UI.

O processo consiste em três etapas. Primeira: o StreamBuilder cria uma inscrição no Stream passado através do método stream.listen. Segunda: a cada evento, o StreamBuilder atualiza o AsyncSnapshot interno e marca o widget como sujo para reconstrução. Terceira: o framework chama a função builder com o novo snapshot, e a UI exibe os dados atuais.

Importante: o StreamBuilder usa StreamSubscription internamente. Se o Stream for passado diretamente, o StreamBuilder se inscreve uma vez durante a inicialização. Se o Stream mudar (por exemplo, durante uma reconstrução do pai), o StreamBuilder cancela a inscrição do fluxo antigo e se inscreve no novo. Esse comportamento é controlado pelos parâmetros initialData e buildWhen, que permitem otimizar o número de reconstruções.

Conclusão: entender o ciclo de vida da inscrição é a base para o uso correto do StreamBuilder. O gerenciamento incorreto de fluxos leva a vazamentos de memória ou dados desatualizados na UI.

ConnectionState: quatro estados do fluxo

A propriedade connectionState do objeto AsyncSnapshot determina em que estágio do processamento do fluxo o StreamBuilder se encontra. Existem quatro estados: none, waiting, active, done.

ConnectionState.none

None é o estado inicial quando o Stream ainda não começou a transmitir dados. Neste estado, snapshot.connectionState é igual a ConnectionState.none e snapshot.data é null. Normalmente, um placeholder ou indicador de espera é exibido neste estado. Se o Stream não fornecer dados iniciais, o StreamBuilder começa neste estado.

ConnectionState.waiting

Waiting é o estado de espera por dados de um fluxo assíncrono. O Stream está ativo, mas os dados ainda não chegaram. Este estado ocorre, por exemplo, ao carregar dados da rede ou ao abrir uma conexão de longa duração. Neste estado, é comum mostrar um CircularProgressIndicator ou um esqueleto de carregamento.

ConnectionState.active

Active — o fluxo está emitindo dados e a UI exibe informações atualizadas. Neste estado, snapshot.hasData é true e snapshot.data contém o valor mais recente do fluxo. Se o fluxo for um Broadcast Stream, o estado ativo pode coexistir com a espera por novos dados.

ConnectionState.done

Done — o fluxo foi concluído, nenhum novo dado chegará. O Snapshot.data contém o último valor transmitido antes do fechamento do fluxo. Se o fluxo foi concluído com sucesso, snapshot.hasError é false. Este estado é usado para exibir o resultado final: uma mensagem como “Carregamento concluído” ou uma transição para a próxima tela.

Conclusão: ao construir a UI através do StreamBuilder, todos os quatro estados devem ser tratados para que a interface exiba corretamente carregamento, dados, erros e conclusão.

Usando StreamController para gerenciar o fluxo

StreamController é uma classe do pacote dart:async que cria e gerencia um Stream. O StreamController permite adicionar dados, tratar erros e fechar o fluxo, controlando seu ciclo de vida.

O StreamController tem dois tipos: single-subscription (um assinante) e broadcast (vários assinantes). Um controlador single-subscription aceita apenas um ouvinte por vez — uma segunda inscrição lançará uma exceção. Um controlador broadcast permite que vários StreamBuilder ouçam o mesmo fluxo simultaneamente, o que é útil para BLoC e estado compartilhado da aplicação.

Ao criar um StreamController via StreamController<T>.broadcast(), os dados adicionados antes da primeira inscrição não são reproduzidos para novos assinantes. Para obter o valor mais recente ao conectar-se, use BehaviourSubject do pacote rxdart, que armazena em cache o último evento.

Após terminar o trabalho com o controlador, é necessário chamar controller.close(). Não chamar close leva a vazamentos de recursos: o fluxo permanece aberto, os assinantes continuam na memória e o GC não libera os objetos associados.

Conclusão: use StreamController com gerenciamento explícito de ciclo de vida. Para fluxos single-subscription, use o controlador padrão; para estado compartilhado, use um controlador broadcast ou BehaviourSubject.

Exemplos de código com StreamBuilder

Exemplo 1 demonstra um temporizador de contagem regressiva usando StreamController e StreamBuilder.

dart
import 'dart:async';

class TimerWidget extends StatefulWidget {
  const TimerWidget({super.key});

  final StreamController<int> controller = StreamController<int>();

  void startTimer() {
    int count = 0;
    Timer.periodic(Duration(seconds: 1), (timer) {
      controller.sink.add(count++);
      if (count > 10) {
        controller.close();
        timer.cancel();
      }
    });
  }
}

No exemplo, um controlador é criado para gerar números de 0 a 10 com intervalo de 1 segundo. Após atingir 10, close é chamado e o fluxo é encerrado. O StreamBuilder, inscrito no fluxo deste controlador, exibirá cada novo valor.

Exemplo 2 — uso do StreamBuilder com um Broadcast Stream para exibir dados de múltiplas fontes.

dart
final StreamController<String> broadcastController =
    StreamController<String>.broadcast();

StreamBuilder<String>(
  stream: broadcastController.stream,
  initialData: 'Waiting for data...',
  builder: (context, AsyncSnapshot<String> snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }
    if (snapshot.hasError) {
      return Text('Erro: ${snapshot.error}');
    }
    return Text('Dados: ${snapshot.data}');
  },
)

O segundo exemplo mostra o tratamento de todos os estados: initialData para exibição inicial, waiting para o indicador de carregamento, hasError para erros e data para resultado bem-sucedido. Este padrão é o padrão para código de produção com StreamBuilder.

Conclusão: use initialData para evitar uma tela vazia no primeiro momento e sempre trate hasError para exibir erros corretamente ao usuário.

Erros comuns ao trabalhar com StreamBuilder

Erro 1: criar um novo Stream a cada reconstrução do pai. Se o Stream for passado através de uma expressão que cria um novo objeto a cada construção, o StreamBuilder cancela a inscrição do antigo e se inscreve no novo fluxo, causando um loop infinito de reconstruções. Solução: usar uma variável remembered ou um StatefulWidget com um Stream fixo.

Erro 2: falta de tratamento de erros. Um Stream pode emitir erros através de controller.sink.addError, e se o builder não verificar snapshot.hasError, o usuário vê uma tela vazia ou carregamento infinito. Solução: verifique sempre hasError e exiba uma mensagem clara.

Erro 3: vazamentos de memória devido a um StreamController não fechado. Se o controlador não for fechado no dispose, o fluxo continua existindo e o GC não libera memória. Solução: chame controller.close() no dispose e ouça o evento done para ações finais.

Erro 4: usar StreamBuilder com uma função builder lenta. Como o builder é chamado a cada evento do fluxo, cálculos pesados dentro dele causam queda de quadros. Solução: mova os cálculos para um isolate separado ou use Stream.map para transformação de dados.

Conclusão: StreamBuilder é uma ferramenta poderosa, mas exigente. Monitore o ciclo de vida do Stream, trate erros e evite operações pesadas no builder.

Perguntas frequentes

Como o StreamBuilder difere do FutureBuilder?

FutureBuilder é projetado para um único resultado assíncrono: ele se inscreve em um Future, recebe um valor e termina. O StreamBuilder se inscreve em um Stream, que pode emitir múltiplos valores ao longo do tempo, e reconstrói a UI a cada novo evento.

O que é AsyncSnapshot no StreamBuilder?

AsyncSnapshot é um objeto imutável que contém o estado atual da inscrição (connectionState), o último valor recebido (data) e um objeto de erro (error) se o fluxo emitiu uma exceção.

Como lidar com erros no StreamBuilder?

Erros são tratados através das propriedades snapshot.hasError e snapshot.error na função builder. Se o fluxo emitir um erro via sink.addError, o AsyncSnapshot recebe o erro, e o builder deve exibir uma mensagem apropriada ou UI de fallback.

Pode-se usar um Stream em vários StreamBuilder?

Sim, se o Stream for broadcast (criado via StreamController.broadcast). Um Stream single-subscription permite apenas um assinante. Para compartilhar um fluxo entre vários widgets, use um controlador broadcast ou o pacote rxdart com BehaviourSubject.

Como evitar a reconstrução do StreamBuilder a cada evento?

Use o parâmetro buildWhen para filtrar quais eventos devem disparar reconstruções da UI. Aplique também Stream.transformer ou Stream.where para filtrar dados antes de passá-los ao StreamBuilder.

Resumo

  • StreamBuilder — um widget para construção reativa de UI a partir de um fluxo de dados assíncrono, suportando atualizações contínuas da interface
  • AsyncSnapshot contém connectionState (none, waiting, active, done), data e error — todos os estados do fluxo
  • StreamController gerencia o ciclo de vida do fluxo: adicionar dados, tratar erros e fechar o fluxo
  • Broadcast Stream permite que vários StreamBuilder se inscrevam em um fluxo, single-subscription apenas um
  • Tratamento de erros é obrigatório: sem verificar hasError, o aplicativo pode ficar preso no estado de carregamento
  • Vazamentos de memória são o problema mais comum: sempre feche StreamController no dispose
  • Recomendação: sempre especifique initialData e trate todos os quatro valores de connectionState para uma UX perfeita

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também