Como lidar com a criação de widgets indesejados?

143

Por várias razões, às vezes o buildmétodo dos meus widgets é chamado novamente.

Eu sei que isso acontece porque um pai atualizou. Mas isso causa efeitos indesejados. Uma situação típica em que causa problemas é quando se usa FutureBuilderdesta maneira:

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: httpCall(),
    builder: (context, snapshot) {
      // create some layout here
    },
  );
}

Neste exemplo, se o método de construção fosse chamado novamente, ele acionaria outra solicitação http. O que é indesejável.

Considerando isso, como lidar com a compilação indesejada? Existe alguma maneira de impedir a chamada de compilação?

Rémi Rousselet
fonte
4
Na documentação do provedor, você vincula aqui dizendo "Consulte esta resposta do stackoverflow, que explica em mais detalhes porque o uso do construtor .value para criar valores é indesejável". No entanto, você não menciona o construtor de valor aqui ou em sua resposta. Você queria vincular a outro lugar?
Suragch

Respostas:

224

O método de compilação foi projetado de forma a ser puro / sem efeitos colaterais . Isso ocorre porque muitos fatores externos podem acionar uma nova compilação de widget, como:

  • Rota pop / push
  • Redimensionamento da tela, geralmente devido à aparência do teclado ou alteração de orientação
  • O widget pai recriou seu filho
  • Um InheritedWidget do widget depende da Class.of(context)alteração ( padrão)

Isso significa que o buildmétodo não deve acionar uma chamada http ou modificar qualquer estado .


Como isso está relacionado à questão?

O problema que você está enfrentando é que seu método de compilação tem efeitos colaterais / não é puro, tornando a chamada de compilação estranha problemática.

Em vez de impedir a chamada de construção, você deve tornar seu método de construção puro, para que possa ser chamado a qualquer momento sem impacto.

No caso do seu exemplo, você transformaria seu widget em uma StatefulWidgetextração da chamada HTTP para a initStatesua State:

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  Future<int> future;

  @override
  void initState() {
    future = Future.value(42);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: future,
      builder: (context, snapshot) {
        // create some layout here
      },
    );
  }
}

Eu já sei disso. Eu vim aqui porque eu realmente quero otimizar reconstruções

Também é possível criar um widget capaz de reconstruir sem forçar seus filhos a construir também.

Quando a instância de um widget permanece a mesma; A vibração propositalmente não reconstrói as crianças. Isso implica que você pode armazenar em cache partes da sua árvore de widgets para evitar reconstruções desnecessárias.

A maneira mais fácil é usar constconstrutores de dardo :

@override
Widget build(BuildContext context) {
  return const DecoratedBox(
    decoration: BoxDecoration(),
    child: Text("Hello World"),
  );
}

Graças a essa constpalavra-chave, a instância de DecoratedBoxpermanecerá a mesma, mesmo que a compilação tenha sido chamada centenas de vezes.

Mas você pode obter o mesmo resultado manualmente:

@override
Widget build(BuildContext context) {
  final subtree = MyWidget(
    child: Text("Hello World")
  );

  return StreamBuilder<String>(
    stream: stream,
    initialData: "Foo",
    builder: (context, snapshot) {
      return Column(
        children: <Widget>[
          Text(snapshot.data),
          subtree,
        ],
      );
    },
  );
}

Neste exemplo, quando o StreamBuilder for notificado sobre novos valores, subtreenão será reconstruído, mesmo que o StreamBuilder / Column o faça. Isso acontece porque, graças ao fechamento, a instância de MyWidgetnão mudou.

Esse padrão é muito usado em animações. Os usos típicos são AnimatedBuildere todas as transições, como AlignTransition.

Você também pode armazenar subtreeem um campo da sua classe, embora seja menos recomendado, pois quebra o recurso de recarga a quente.

Rémi Rousselet
fonte
2
Você poderia explicar por que o armazenamento subtreeem um campo de classe é recarregado a quente?
MFeinstein 14/03/19
4
Um problema que estou StreamBuilderenfrentando é que, quando o teclado aparece, a tela muda, então as rotas precisam ser reconstruídas. Então, StreamBuilderé reconstruído e um novo StreamBuilderé criado e ele assina o stream. Quando um StreamBuilderassina um stream, snapshot.connectionStatetorna-se o ConnectionState.waitingque faz com que meu código retorne CircularProgressIndicatorae snapshot.connectionStatemuda quando há dados, e meu código retorna um widget diferente, o que faz a tela piscar com coisas diferentes.
MFeinstein 14/03/19
1
Decidi fazer um StatefulWidget, assinar o streamon initState()e definir o currentWidgetcom setState()como streamenvia novos dados, passando currentWidgetpara o build()método Existe uma solução melhor?
MFeinstein 14/03/19
1
Estou um pouco confuso. Você está respondendo sua própria pergunta, no entanto, a partir do conteúdo, não parece.
sgon00
8
Dizer que uma construção não deve chamar um método HTTP derrota completamente o exemplo muito prático de a FutureBuilder.
TheGeekZn 17/06/19
6

Você pode impedir chamadas de build indesejadas usando estas

1) Criar classe Statefull filho para uma pequena parte individual da interface do usuário

2) Use Provedor de biblioteca, de modo a usá-lo você pode parar indesejado método de construção chamada

Nestes abaixo chamada de método de construção situação

  • Depois de chamar initState
  • Depois de chamar didUpdateWidget
  • quando setState () é chamado.
  • quando o teclado está aberto
  • quando a orientação da tela mudou
  • O widget pai é construído, o widget filho também é reconstruído
Sanjayrajsinh
fonte
0

Flutter também tem ValueListenableBuilder<T> class . Ele permite que você reconstrua apenas alguns dos widgets necessários para o seu propósito e pule os widgets caros.

você pode ver os documentos aqui documentos de flutter do ValueListenableBuilder
ou apenas o código de exemplo abaixo:

  return Scaffold(
  appBar: AppBar(
    title: Text(widget.title)
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('You have pushed the button this many times:'),
        ValueListenableBuilder(
          builder: (BuildContext context, int value, Widget child) {
            // This builder will only get called when the _counter
            // is updated.
            return Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Text('$value'),
                child,
              ],
            );
          },
          valueListenable: _counter,
          // The child parameter is most helpful if the child is
          // expensive to build and does not depend on the value from
          // the notifier.
          child: goodJob,
        )
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
    child: Icon(Icons.plus_one),
    onPressed: () => _counter.value += 1,
  ),
);
Taba
fonte