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;
Futuro  fetchName () 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, AsyncSnapshot  instantâ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:

  1. nenhum : talvez com alguns dados iniciais
  2. esperando : a operação assíncrona começou. Os dados são normalmente nulos
  3. ativo : os dados não são nulos e podem mudar com o tempo
  4. 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 .