Fala pessoal, tudo certo?
Notei que bastante gente tem problemas na utilização do FutureBuilder, então resolvi criar esse post.
Como esse cara funciona?
O FutureBuilder executa a função informada em sua propriedade future.
Até aqui tudo certo, mas temos um problema quando envolve o setState: A função é carregada novamente.
Por quê esse problema acontece?
O Flutter trabalha com duas árvores:
Árvore de Widgets
A Árvore de Widgets é o código que escrevemos para montar o layout.
Árvore de Elementos
A Árvore de Elementos é um "esqueleto" da UI, uma das sacadas para o Flutter ser tão rápido em desenhar na tela e atualizar seus desenhos nas animações.
O setState executa novamente o método build. A estrutura de Widgets se vira muito bem com isso e não reconstrói toda a UI, mas métodos de dentro do build são executados novamente.
Essa é a causa do problema, pois o pessoal costuma colocar a função diretamente na propriedade future do FutureBuilder.
Como resolver esse problema?
Essa pergunta é fácil de responder, basta criar uma variável do tipo Future<Tipo>
e iniciá-la no InitState de seu widget. Dessa forma, podemos utilizar essa variável na propriedade future do FutureBuilder, impedindo que a função seja executada novamente.
Resumão
Sempre que quiser trabalhar com FutureBuilder, não coloque a função diretamente na propriedade future, pois o setState sempre executa o método build, iniciando novamente sua função.
Crie uma variável do tipo Future<Tipo>
e inicie a função no InitState de seu widget;
Utilize essa variável na propriedade future, dessa forma não iniciará novamente a função com um setState;
Exemplo
- Criamos dois botões: Um para dar setState e outro para executar novamente a função assíncrona;
- Quando os segundos da hora atual forem par, será apresentado um quadrado verde;
- Quando os segundos da hora atual forem ímpar, será apresentado um quadrado vermelho;
- O número capturado, e os setStates serão printados no DEBUG CONSOLE;
- O código está comentado para facilitar o entendimento;
import 'package:flutter/material.dart';
class FutureBuilderExemple extends StatefulWidget {
@override
_FutureBuilderExempleState createState() => _FutureBuilderExempleState();
}
class _FutureBuilderExempleState extends State<FutureBuilderExemple> {
Future<bool> _future;
@override
void initState() {
super.initState();
_future = myFuture();
}
Future<bool> myFuture() async {
await Future.delayed(Duration(seconds: 2)); //Aguarda 2 segundos para prosseguir com o resto do código
var currentSeccond = DateTime.now().second; //Salva o segundo da hora atual
print("currentSeccond = $currentSeccond"); //Printa o número no DEBUG CONSOLE
return currentSeccond.isEven; //Retorna TRUE quando o número for par, FALSE quando ímpar
}
@override
Widget build(BuildContext context) {
print("setState executado");
return Material(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FutureBuilder<bool>(
future: _future,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: CircularProgressIndicator(),
);
else
return Container(
width: 100,
height: 100,
color: snapshot.data ? Colors.green : Colors.red,
);
},
),
RaisedButton(
child: Text("setState"),
onPressed: () {
setState(() {}); //Apenas um setState para mostrar que o método não será chamado novamente.
},
),
RaisedButton(
child: Text("Reload function"),
onPressed: () {
setState(() {
_future = myFuture(); //Recarrega a função
});
},
),
],
),
);
}
}
Valeu pessoal, qualquer dúvida é só postar aqui nos comentários.
Até a próxima!