Escolha uma Página

Anteriormente nós explicamos como funciona o BLoC e iniciamos um exemplo, se você ainda não leu a primeira parte, clique aqui.
Agora nós iremos gerenciar o estado da nossa View usando Fluxos (Streams).

Streams

Utilizando o padrão Consumer, conseguimos dividir a regra de negócio da view e conectamos os dois (Fazendo assim o BLoC). Mas ainda temos processamento na View.

Temos dois Widgets que precisam ser atualizados mediante ao movimento do Slider, porém um deles concatena o valor double em uma String “Value: “. Nesse exemplo isso não seria um problema, mas imagine um cenário hipotético em que essa String precise ser traduzida para o português, ficando “Valor:”, e ai sim concatenar com o double. Para resolver esse problema poderíamos criar um método no nosso ValueBloc que recebe um double e retorna uma String. Porém um dos motivos do BLoC existir também é a componentização da regra de negócio, isso é, podermos reaproveitar esse mesmo BLoC em um outro contexto.

Para resolver isso de forma elegante, podemos pensar em um fluxo que recebe um double como entrada, e que tenha duas saídas, uma do tipo double, e a segunda do tipo String. Observe a representação gráfica abaixo.



Temos então a representação de um fluxo que recebe um double (0.5) como input, e se ramifica para ter 2 outputs.

Agora precisamos preparar o nosso código para receber um dado e enviar 2 streams de tipos diferentes.

StreamController no BLoC

O StreamController é um objeto que usamos para ter um controle de fluxo. Ele é capaz de receber e transmitir dados para um ouvinte. Podemos enviar um dado usando o método add() e “escutar” usando a propriedade “stream”. Vamos instanciar um StreamController no nosso ValueBloc.


StreamController<double> _valueController = StreamController();

Note que tipamos o Objeto StreamController para que ele receba um valor double como entrada, isso é bom pois agora podemos preparar nossas streams já esperando um valor double.
No método onChangeValue, não vamos utilizar o método notifyListeners(), porque iremos notificar diretamente no nosso streamController quando entrar um valor, substituiremos então por: _valueController.add(value);

O método _valueController.add envia um dado e notifica todos que estão escutando a sua stream que houve uma alteração.
Agora criaremos as saídas e segmentaremos para os dois tipos que a nossa view espera. Vamos usar a sintaxe getter do Dart para ter uma variável para cada stream.


  Stream<double> get valueOut => _valueController.stream;

  Stream<String> get valueStringOut =>
             _valueController.stream.map((v) => "Valor: ${v.toStringAsPrecision(1)}");

A variável valueOut recebe a stream do próprio StreamController, visto que precisamos exatamente do mesmo double que foi enviado.

A variável valueStringOut recebe a stream do StreamController, porém é chamada a propriedade map para fazer um re-mapeamento dos dados.
O Map funciona da mesma forma que o map do List, ou seja, recebe uma função que como parâmetro tem o dado atual e pode retornar qualquer valor alterando o tipo da Stream. Nesse caso específico, concatenamos o double (v) aplicando a propriedade toStringAsPrecision que define quantas casas decimais o double pode ter. Como o valor de retorno da função é uma String, a Stream passa a esperar uma String. Isso que chamamos de mutação de dados, e está totalmente presente na Programação Reativa.

Existe algo que teremos que nos atentar quando o assunto for StreamController, precisamos fechar a instância quando não estivermos mais utilizando, pois senão houver um fechamento, o StreamController pode ficar aberto mesmo se a aplicação for fechada, prejudicando a memória do dispositivo. Então temos que chamar o método close() em algum momento. Porém essa é uma das tarefas que o package bloc_pattern automatiza para nós. Basta sobrescrever o método dispose() da classe herdada BlocBase, e chamando o fechamento do StreamController. O dispose será chamado automaticamente quando a sua aplicação fechar.

Feito isso, o código do ValueBloc agora ficou assim:

Agora vamos conectar nossa regra de negócio com a view, mas dessa vez utilizando Streams.

StreamBuilder na View

O StreamBuilder é um Widget que recebe uma Stream e se reconstrói todas as vezes que recebe um novo dado, substituiremos o Widget Consumer por ele:


StreamBuilder<String>(
            stream: valueBloc.valueStringOut,
            initialData: "",
            builder: (BuildContext context, snapshot) {
              return _textValue(snapshot.data);
            },
          ),

Com o StreamBuilder temos mais opções para customizar nossos widgets. Vamos analisar as 3 principais propriedades.
stream: Recebe a stream.
initialData: Recebe um dado inicial. Se não houver declaração, o valor recebido será null, e isso pode quebrar alguns widgets.
builder: recebe uma função com o contexto da aplicação e o snapshot e pede como retorno um Widget que será rebuildado toda vez que a stream receber um novo dado. Para pegar o dado que vem da stream basta acessar a propriedade snapshot.data.

O código completo da View fica assim:

Toda a sintaxe está correta, porém quando iniciamos a aplicação temos o seguinte erro:

É comum ver esse erro em aplicações que usam o StreamController, pois ele só pode ser escutado por um elemento, nesse caso só funcionaria apenas com um StreamBuilder. (Experimente retirar um StreamBuilder para ver o erro sumir).

Para podermos escutar a nossa stream em mais de um StreamBuilder precisaremos instanciar o StreamController com a constante broadcast, isso torna a Stream “escutável” por mais de um ouvinte.


StreamController<double> _valueController = StreamController.broadcast();

Pronto! Agora temos nosso aplicativo com BLoC, só que dessa vez conectado por Streams.

Note que essa segunda abordagem é um pouco mais verbosa, porém dessa forma podemos customizar melhor os fluxos, e isso tornará nossa view e BLoC mais independentes e reutilizáveis.

No próximo POST iremos falar sobre Programação Reativa e como utilizar esse novo paradigma com o BLoC.