Há muitos casos em que precisamos construir um widget de forma assíncrona para refletir o estado correto do aplicativo ou dos dados. Um exemplo comum é buscar dados de um endpoint REST.
Neste tutorial, lidaremos com esse tipo de solicitação usando Dart and Flutter . O Dart é uma linguagem de thread único que aproveita os loops de eventos para executar tarefas assíncronas. O método de construção no Flutter, no entanto, é síncrono.
Vamos começar!
O ciclo de eventos do Dart
Depois que alguém abre um aplicativo, muitos eventos diferentes ocorrem em nenhuma ordem previsível até que o aplicativo seja fechado. Cada vez que um evento acontece, ele entra em uma fila e espera para ser processado. O loop de eventos Dart recupera o evento no topo da fila, processa-o e dispara um retorno de chamada até que todos os eventos na fila sejam concluídos.
As classes Future
e Stream
e as async
e await
palavras-chave no Dart são baseadas neste loop simples, tornando programação assíncrona possível. No snippet de código abaixo, a entrada do usuário está respondendo à interação em um widget de botão usando retornos de chamada:
ElevatedButton ( criança: Texto ("Olá, equipe"), onPressed: () { const url='https://majidhajian.com'; final meuFuturo=http.get (url); meuFuturo.então ((resposta) { //(3) if (response.statusCode==200) { imprimir ('Sucesso!'); } }); }, )
widget ElevatedButton
O widget ElevatedButton
fornece parâmetros convenientes para responder a um botão sendo pressionado. Assim que o evento onPressed
é disparado, ele espera na fila. Quando o loop de eventos atinge esse evento, a função anônima é executada e o processo continua.
Criação de widgets Flutter
Agora que aprendemos como a programação assíncrona funciona no Dart, entendemos o molho secreto por trás do Flutter. Agora, podemos lidar com as solicitações futuras
e construir nossos widgets Flutter.
Como o método build
no Flutter é executado de forma síncrona, precisamos encontrar uma maneira de garantir que o aplicativo construirá widgets com base nos dados que serão recebidos no futuro.
StatefulWidget
Uma abordagem é usar StatefulWidget
e defina o estado enquanto as informações são obtidas:
import'dart: convert'; import'package: flutter/material.dart'; importe'pacote: http/http.dart'como http; FuturofetchName () assíncrono { final Uri uri=Uri.https ('maijdhajian.com','/getRandonName'); nome http.Response final=espera http.get (uri); return jsonDecode (name.body); } class MyFutureWidget extends StatefulWidget { @sobrepor _MyFutureWidgetState createState ()=> _MyFutureWidgetState (); } class _MyFutureWidgetState extends State { Fragmento? valor; @sobrepor void initState () { super.initState (); //A função fetchName é assíncrona para OBTER dados http fetchName (). then ((resultado) { //Assim que recebermos nosso nome, iniciamos a reconstrução. setState (() { valor=resultado; }); }); } @sobrepor Construção de widget (contexto BuildContext) { //Quando o valor é nulo, mostra o indicador de carregamento. if (value==null) { retornar const CircularProgressIndicator (); } return Text ('Valor buscado: $ valor'); } }
Neste exemplo, você deve ter notado que não tratamos adequadamente as possíveis exceções, que podemos resolver adicionando uma variável error
. O processo acima funcionará, mas podemos melhorá-lo.
widget FutureBuilder
FutureBuilder fornece uma maneira mais limpa e melhor de lidar com o future
no Flutter. FutureBuilder
aceita um futuro
e constrói um widget quando os dados são resolvidos:
const FutureBuilder ({ Chave? chave, este futuro, this.initialData, necessário this.builder, }): assert (builder!=null), super (key: key);
Vamos dar uma olhada em como o widget FutureBuilder
funciona:
FutureBuilder <> ( futuro: FUTURO, intialData: null, construtor: (contexto BuildContext, AsyncSnapshotinstantâneo) { } );
O segundo parâmetro na função build
é um tipo de AsyncSnapshot
com um tipo de dados especificado. Por exemplo, no código acima, definimos String
.
O instantâneo é uma representação imutável da interação mais recente com um cálculo assíncrono. Possui várias propriedades. Quando ocorre uma computação assíncrona, é útil saber o estado da conexão atual, o que é possível por meio de snapshot.connectionState
.
O connectionState
tem quatro fluxos usuais:
-
nenhum
: talvez com alguns dados iniciais -
esperando
: a operação assíncrona começou. Os dados são normalmente nulos -
ativo
: os dados não são nulos e podem mudar com o tempo -
concluído
: os dados não são nulos
snapshot.data
retorna os dados mais recentes e snapshot.error
retorna o objeto de erro mais recente. snapshot.hasData
e snapshot.hasError
são dois getters úteis que verificam se um erro ou dados foram recebidos.
FutureBuilder
é um StatefulWidget
que usa o estado como um instantâneo. Olhando para a fonte FutureBuilder
código , podemos reconhecer o instantâneo inicial mostrado no snippet de código abaixo:
_snapshot=widget.initialData==null ? AsyncSnapshot.nothing () : AsyncSnapshot .withData (ConnectionState.none, widget.initialData como T);
Enviamos um futuro
ao qual o widget se inscreve, atualizando o estado com base nele:
void _subscribe () { if (widget.future!=null) { objeto final callbackIdentity=Object (); _activeCallbackIdentity=callbackIdentity; widget.future!.então((dados T) { if (_activeCallbackIdentity==callbackIdentity) { setState (() { _snapshot=AsyncSnapshot .withData (ConnectionState.done, data); }); } }, onError: (Erro de objeto, StackTrace stackTrace) { if (_activeCallbackIdentity==callbackIdentity) { setState (() { _snapshot=AsyncSnapshot .withError (ConnectionState.done, erro, stackTrace); }); } }); _snapshot=_snapshot.inState (ConnectionState.waiting); } }
Quando descartamos o widget, ele cancela a inscrição:
@override void dispose () { _Cancelar subscrição(); super.dispose (); } void _unsubscribe () { _activeCallbackIdentity=null; }
Vamos refatorar nosso exemplo acima para usar FutureBuilder
:
class MyFutureWidget extends StatelessWidget { @sobrepor Construção de widget (contexto BuildContext) { return FutureBuilder ( futuro: getName (), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState==ConnectionState.waiting) { retornar CircularProgressIndicator (); } if (snapshot.hasData) { return Text (snapshot.data); } return Container (); }, ); } }
Observe que usei a função getName ()
diretamente em meu FutureBuilder
dentro do método build
.
Cada vez que o pai do FutureBuilder
é reconstruído, a tarefa assíncrona será reiniciada, o que não é uma boa prática.
Resolva este problema movendo o futuro
a ser obtido o mais cedo possível-por exemplo, durante initState
em um StatefulWidget
:
class MyFutureWidget extends StatefulWidget { @sobrepor _MyFutureWidgetState createState ()=> _MyFutureWidgetState (); } class _MyFutureWidgetState extends State{ Futuro _dataFuture; @sobrepor void initState () { super.initState (); _dataFuture=getName (); } @sobrepor Construção de widget (contexto BuildContext) { return FutureBuilder ( futuro: _dataFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState==ConnectionState.waiting) { retornar CircularProgressIndicator (); } if (snapshot.hasData) { return Text (snapshot.data); } if (snapshot.hasError) { return Text ('Há algo errado!'); } return SizedBox (); }, ); } }
initState ()
é chamado sempre que o widget é criado. Portanto, a função getName future
será memorizada em uma variável. Embora meu widget possa alterar o estado e reconstruir a cada vez, meus dados permanecerão intactos.
widget StreamBuilder
Também vale a pena dar uma olhada em StreamBuilder
, outro widget que lida com stream
. StreamBuilder
e FutureBuilder
são quase idênticos. No entanto, o StreamBuilder
entrega dados periodicamente, então você precisa ouvi-los com mais frequência do que FutureBuilder
, que deve ser ouvido apenas uma vez.
O widget StreamBuilder
se inscreve e cancela automaticamente a inscrição no stream
. Ao descartar um widget, você não precisa se preocupar em cancelar a assinatura, o que pode causar um vazamento de memória:
@override Construção de widget (contexto BuildContext) { return StreamBuilder( stream: dataStream, construtor: (contexto BuildContext, AsyncSnapshot instantâneo) { }, ); }
Conclusão
Neste tutorial, você aprendeu como realizar callbacks assíncronos no Flutter para buscar dados de um endpoint REST. A programação assíncrona é uma força poderosa que economiza o tempo e a energia dos desenvolvedores. O Flutter oferece ferramentas exclusivas que simplificam ainda mais o processo.
Construir widgets com FutureBuilder
e StreamBuilder
é um grande benefício de usar Dart e Flutter para estruturar sua IU. Esperançosamente, agora você entende como esses dois widgets funcionam no nível fundamental por meio do loop de eventos Dart.
A postagem Callbacks assíncronos com Flutter FutureBuilder apareceu primeiro em LogRocket Blog .