Futures vs Streams — Fundamentos
Futures e Streams são elementos trazidos junto da linguagem Dart (neste contexto) e que permitem obter dados de forma assíncrona em aplicações.
Assume-se como assíncrono a qualquer evento que pode ocorrer de forma paralela com o decurso normal de outras actividades de nossa aplicação, no caso de Flutter podemos exemplificar como o facto de construirmos todos os widgets na ordem, porém, os dados da Lisview no corpo da página são apenas apresentados momentos depois, isto porque os eventos assíncronos (Futures e Streams no caso), são os últimos a apresentar em uma cadeia de código dart, contudo os dados estão lendo no mesmo momento em que os restantes widgets são construídos.
Para perceber melhor como cada um deles opera irei dividir o artigo em três secções:
- Futures;
- Streams; e
- Futures VS Streams (Conclusão).
Este artigo é o primeiro de uma série de artigos (2):
- Futures vs Streams — Fundamentos (este artigo)
- Futures vs Streams — O básico
Futures
Representam a possibilidade de existência de um valor futuro, que poderá resultar no valor como tal ou erro.
Os futures resultam em um único valor após que são chamados à acção .
Mas como funciona?
Os futures funcionam basicamente como handlers de dados que nós precisamos a dada altura e recebem apenas um dado de controlo (eu chamo assim) para lhe retornar o resultado, podendo este dado de controlo ser por exemplo uma requisição http ou dado armazenado no SharedPreferences e a partir deste mesmo dado nós precisamos pegar a informação que vá de acordo com o valor passado, ou seja, o único valor que resulta de um future pode não ser o resultado a ser ilustrado na tela. Explico mais adiante; Há, entretanto, que salientar que este valor esperado pode resultar também em um erro, o que nos leva a perceber, se estivermos bastante atentos que os Futures possuem três estados (Uncompleted — Incompleto, Completed with a value — Completo com valor e Completed with a error — Completo com erro).
- Uncompleted — o momento em que definimos o nosso future e o código mantém as outras acções a correr enquanto espera pelo resultado do stream.
- Completed with a value — o stream já detém da resposta ao pedido do valor desejado e é positiva (o valor em sim, aka error=false).
- Completed with a error — o stream já detém da resposta ao pedido do valor desejado e é negativa (retorna um erro).
Imaginemos um caso em que fazemos uma requisição http para meu último artigo até o momento que este foi ao ar e o resultado for 200 ‘ok’, ou seja, positiva ele retorna o título do artigo e se ocorrer algum erro, seja o 403 ‘forbidden’ ou 404 ‘page not found’ retorna uma mensagem a indicar ao utilizador que ocorreu um erro. Como podemos ver o future, no caso é requisição http (o link), o valor que o future espera é o resultado dessa requisição e os dados usados para ilustrar ao utilizador podem não ser directamente inerentes ao valor retornado, podendo apresentar a partir de uma única requisição http uma lista, porém há que perceber que a apresentação dessa lista depende do (único) valor passado como future.
FutureBuilder
Bom, os futures não são widgets e não retornam widgets! Então como é suposto trabalhar com eles em Flutter, se tudo em Flutter são widgets?
Dart nativamente não opera com widgets, essa é uma particularidade do Flutter, portanto, é necessário que os casos de uso aplicados com recursos a linguagem dart possam apresentar resultados “adaptados” a forma de operação do Flutter, eis que temos o FlutterBuilder para operar com os pedidos/respostas assíncronas dos futures.
De forma simples e directa, FutureBuilder é um widget que interage e constrói-se de acordo com o resultado do future correspondente.
Em termos práticos olhemos para o exemplo antes apresentado, onde temos os casos de sucesso ou contrário e pretendemos apresentar agora em uma aplicação real, aplicaremos o future ao nosso FutureBuilder e com recurso ao builder do FutureBuilder nós conseguimos aceder ao valor como um snapshot que este nos fornece como resultado e aplicamos ao contexto da aplicação e são aplicados casos específicos a FutureBuilder que permitam lidar com cada um dos mesmos.
Streams
Tal e qual os futures, os streams permitem receber valores ou erros e controlar seus estados, porém, no caso dos streams permitem-nos receber uma sucessão destes valores ou erros (é possível receber erros e valores) a partir de uma única subscrição ou chamada.
Possui para atentar às mudanças que vão ocorrendo aos resultados dos streams o controller, sink e o próprio stream, de modo a controlar as mudanças de resposta, pegar no resultado da valor a esperar naquele momento e adicionar ao stream e manter os valores/erros obtidos ao longo do processo em que este está subscrito, respectivamente.
Para obter os valores presentes em um stream usa-se o método .listen que retorna os valores presentes no stream permitindo tomar uma acção de acordo com cada um desses valores.
Imaginemos que pretendemos pegar em múltiplos artigos, ou seja, múltiplas requisições http, aí recorremos a Streams que nos permitiram descobrir de acordo com o resultado de cada requisição específica que acção pretendemos executar, Podemos obter resultado como {título, título, erro, título}, porém os streams permitem cancelar ou não a leitura dos dados na ocorrência dos erros pelo método .cancelOnError.
StreamBuilder
A implementação do StreamBuilder é também similar a do FutureBuilder, com o diferencial de permitir a partir de uma “construção” deste widget ele retornar vários widgets, geralmente está associado a um widget como os Listview para manter todos os resultados obtidos na tela, ou seja, podemos ter uma lista com os títulos dos artigos dispostos em nossa aplicação.
Conclusão
Pode-se então concluir que os futures são pra operar por meio de valores singulares e os streams a uma cadeia de valores assíncronos, ambos dispostos a mudanças, porém, os streams atendem a mudanças recorrentes dos elementos que ele possui. O StatefulWidget é parente de ambos.
Uma analogia bastante recorrente é a do objecto/variável e iterator quando lidamos com dados síncronos.
Não consideramos nenhuma solução melhor que outra, apenas é importante decidir qual escolher para cada caso de uso específico, reduzindo a complexidade e uso desnecessário dos recursos de computação ou mesmo aplicação correcta de uma funcionalidade.
Espero que tenha aprendido com este artigo e que se tenha divertido enquanto lia.
Obrigado por acompanhar até ao fim e espero por você nos próximos artigos.
Para questões e sugestões esteja a vontade para tal nos comentários, email igorlsambo1999@gmail.com ou twitter @lsambo02.
Não se esqueça de contribuir para os projectos que se encontram no meu repositório do github, assim aprendemos todos!
Obrigado e até ao próximo artigo!!!