Aplicativos reativos em Flutter - Parte III

Vamos falar sobre fluxos em Flutter e como os fluxos podem ser construídos para se adaptarem à interface do usuário.

Você fica desesperado como eu quando ouve sobre Streams, Observables, Redux, Flux? kk, vamos tentar mudar isso? Uma das vantagens do Flutter é que ele usa visualizações reativas. Assim, você pode avançar para o próximo nível aplicando também princípios reativos ao modelo de dados do seu aplicativo.

Então vamos falar sobre fluxos: se você está mais familiarizado com o termo Observables, saiba que Streams e Observables estão intimamente relacionados e são basicamente intercambiáveis.

Quando você pensa em desenvolvimento de aplicativos, fluxos estão em toda parte. Cada entrada do usuário é um fluxo de eventos assíncronos: toda interação com o sistema subjacente é um fluxo de eventos assíncronos; toda interação com o mundo externo através da internet ou rede é um fluxo de eventos assíncronos. E, obviamente, se você pensar sobre isso, todas as suas atualizações para a interface do usuário são um fluxo de eventos assíncronos. 

Portanto, pessoas que estão familiarizadas com o ReactiveX e programação reativa, em geral, provavelmente sabe onde estou indo com isso. 

A boa notícia é que o Dart tem um suporte realmente bom para streams: mapeamento, transformação de fluxo... 

Além disso, há um pacote chamado rxdart, que constrói as extensões reativas no topo  dos fluxos inerentes em Dart. 

Agora você pode estar perguntando: como isso se encaixa em Flutter? Bem, o Flutter tem esse widget chamado StreamBuilder, que é um widget que usa um fluxo como entrada stream e um método de construtor builder que será reconstruído toda vez que houver um novo valor no fluxo. 

StreamBuilder <T> (
stream: input,
builder: (context, snapshot){
...
},
)

Bom, mas não seria mais incrível se tivéssemos algum tipo de padrão arquitetural para construir nossos aplicativos? Imagine que você tem um aplicativo grande e tem alguns widgets que levam a entrada do usuário, então eles têm fluxos de eventos assíncronos próximos do usuário. E você tem outros widgets que pode estar em qualquer outro lugar no widget que tenta atualizar sempre que há uma mudança de estado. Você não pode simplesmente ligar os dois juntos, você precisa de algo no meio, e é exatamente isso que BloC faz. Você precisa transmitir os eventos dos widgets que tem botões e entradas para a lógica de negócios BloC que publica fluxos de alterações de dados para widgets que estejam interessados ocorrendo então a reconstrução. 

Particulamente, você está publicando streams separados, então o outro widget pode estar interessado em um outro fluxo, então, basta que se inscreva neste fluxo para escutar as mudanças. Assim, não irá reconstruir sempre, mas só quando houver mudanças nesse modelo específico.

É perceptível que, conforme imagem acima, há entradas e saídas de fluxos dentro de business logic. Como seria isso em Dart? utilizamos Sink para as entradas e Stream para as saídas:

class Test {
Sink<Event> input;
Stream<Data> output;
}

Em Flutter esse esquema é chamado de Business Logic Component - BLoC. Vamos partir para a prática?

Vamos codificar o BLoC de um carrinho de compras:

class CartBloc {
final _cart = Cart();

// Fluxo de entrada que podemos acessar externamente
Sink<Product> get addition => _addController.sink;

// Controlador de fluxo
final _additionController = StreamController[Product]();

//adiciona a saída de fluxo que é um itemCount
Stream<int> get itemCount = _itemCountSubject.stream;

// BehaviorSubject vem do ReactX é um tipo diferente de controlador de fluxo que tem memória do seu valor mais recente
final _itemCountSubject = BehaviorSubject<int>();

}

Primeiramente criamos um fluxo de entrada com sink chamado de _addController, depois apoiamos ele em um controlador de fluxo. StreamController é apenas uma classe básica da biblioteca de fluxo em Dart que nos ajuda a ouvir o fluxo das coisas que estão vindo de fora.

Como usá-lo no aplicativo? acrescentaremos final cartBloc = CartProvider.of(context) no widget que deseja acessar o BLoC.

class ProductGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cartBloc = CartProvider.of(context);
return GridView.count(
crossAxisCount: 2,
children: catalog.products.map((product) {
return ProductSquare(
product: product,
onTap: () {
cartBloc.additionController.add(product);
}
);
}).toList(),
);
}
}

E, para poder adicionar algo nesse fluxo, basta digitar o cartBloc.additionController.add(product)

E para ouvirmos o fluxo do itemCount, vamos utilizar o StreamBuilder

class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cartBloc = CartProvider.of(context);
return Scaffold(
appBar: AppBar(
title: Text("Bloc"),
actions: +Widget+[
StreamBuilder<int>(
stream: cartBloc.itemCount,
initialData: 0,
builder: (context, snapshot) = CartButton(
itemCount: snapshot.data,
onPressed: (){

}
)
)
],
),
);
}
}

Agora, o que precisamos fazer é mesclar os streams juntos e é aqui que a mágica acontece porque tem vários produtos chegando e o que precisamos fazer é ouvir as mudanças com o listen e depois adicionar esses produtos com add() no carrinho:

class CartBloc {
final _cart = Cart();

// Fluxo de entrada que podemos acessar externamente
Sink <Product> get addition => _addController.sink;

// Controlador de fluxo
final _additionController = StreamController[Product]();

//adiciona a saída de fluxo que é um itemCount
Stream[int] get itemCount = _itemCountSubject.stream;

// BehaviorSubject vem do ReactX é um tipo diferente de controlador de fluxo que tem memória do seu valor mais recente
final _itemCountSubject = BehaviorSubject[int]();

CartBloc() {
// ouve o fluxo
_additionController.stream.listen(_handle);
}

void _handle(Product product) {
// adiciona o produto no carrinho
_cart.add(product);
//
atualiza o itemCount quando adiciona um produto
_itemCountSubject.add(_cart.itemCount);
}

}

Então, como você pode ver, conseguimos implementar esse negócio muito rapidamente. É um pouco mais complicado, mas dá para fazer muitas coisas legais.

Com esta série de artigos aprendemos que setState() funciona muito bem com aplicativos pequenos, com poucas arvores; já ScopedModel é uma ferramenta fantástica para um modelo, que se torna relativamente simples ser passado para uma ou outra árvore; e redux que é uma excelente biblioteca utilizada pelo flutter para realizar as mudanças estudadas neste tutorial, ou seja, Widgets + Streams = Reactive.