Come gestire la creazione di widget indesiderati?


143

Per vari motivi, a volte il build viene richiamato metodo dei miei widget.

So che succede perché un genitore si è aggiornato. Ma questo provoca effetti indesiderati. Una situazione tipica in cui causa problemi è quando si utilizza in FutureBuilderquesto modo:

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

In questo esempio, se il metodo build dovesse essere richiamato di nuovo, si innescherebbe un'altra richiesta http. Che è indesiderato.

Considerando questo, come gestire build indesiderate? Esiste un modo per impedire la chiamata build?



4
Nella documentazione del provider si collega qui dicendo "Vedi questa risposta di StackOverflow che spiega in dettaglio perché non è necessario utilizzare il costruttore .value per creare valori." Tuttavia, non menzioni il costruttore del valore qui o nella tua risposta. Intendevi collegarti da qualche altra parte?
Suragch

Risposte:


225

Il metodo di costruzione è progettato in modo tale che dovrebbe essere puro / senza effetti collaterali . Questo perché molti fattori esterni possono innescare una nuova build di widget, come:

  • Percorso pop / push
  • Ridimensionamento dello schermo, in genere dovuto all'aspetto della tastiera o al cambio di orientamento
  • Il widget padre ha ricreato il figlio
  • In EreditatoWidget il widget dipende dalla Class.of(context)modifica ( modello)

Ciò significa che il buildmetodo non deve attivare una chiamata http o modificare alcuno stato .


In che modo ciò è legato alla domanda?

Il problema che stai affrontando è che il tuo metodo di build ha effetti collaterali / non è puro, il che rende problematica la chiamata di build estranea.

Invece di impedire la chiamata build, dovresti rendere puro il tuo metodo build, in modo che possa essere chiamato in qualsiasi momento senza impatto.

Nel caso del tuo esempio, trasformeresti il ​​tuo widget in un StatefulWidgetestratto quindi quella chiamata HTTP al initStatetuo 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
      },
    );
  }
}

Lo so già. Sono venuto qui perché voglio davvero ottimizzare le ricostruzioni

È anche possibile creare un widget in grado di ricostruire senza forzare anche i suoi figli a costruire.

Quando l'istanza di un widget rimane la stessa; Flutter non ricostruirà intenzionalmente i bambini. Implica che è possibile memorizzare nella cache parti dell'albero del widget per impedire ricostruzioni non necessarie.

Il modo più semplice è usare i constcostruttori di freccette :

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

Grazie a quella constparola chiave, l'istanza di DecoratedBoxrimane invariata anche se build è stata chiamata centinaia di volte.

Ma puoi ottenere lo stesso risultato 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,
        ],
      );
    },
  );
}

In questo esempio, quando StreamBuilder riceve una notifica di nuovi valori, subtreenon verrà ricostruito anche se StreamBuilder / Column lo fa. Succede perché, grazie alla chiusura, l'istanza diMyWidget non è cambiata.

Questo modello è molto usato nelle animazioni. Usi tipici sono AnimatedBuildere tutte le transizioni come AlignTransition.

Potresti anche archiviare subtreein un campo della tua classe, anche se meno raccomandato in quanto interrompe la funzione di ricarica rapida.


2
Potresti spiegare perché l'archiviazione subtreein un campo di classe interrompe il hot-ricaricamento?
mFeinstein

4
Un problema che sto riscontrando StreamBuilderè che quando appare la tastiera la schermata cambia, quindi i percorsi devono essere ricostruiti. Quindi StreamBuilderviene ricostruito e StreamBuilderviene creato un nuovo e si abbona al stream. Quando un StreamBuilderabbonamento a stream, snapshot.connectionStatediventa ciò ConnectionState.waitingche fa sì che il mio codice ritorni a CircularProgressIndicator, e poi snapshot.connectionStatecambia quando ci sono dati, e il mio codice restituirà un widget diverso, il che fa tremolare lo schermo con cose diverse.
mFeinstein

1
Ho deciso di fare un StatefulWidget, iscrivermi a streamon initState()e impostare il currentWidgetcon setState()come streaminvia nuovi dati, passando currentWidgetal build()metodo. C'è una soluzione migliore?
mFeinstein

1
Sono un po 'confuso. Stai rispondendo alla tua domanda, tuttavia dal contenuto, non sembra.
sgon00,

8
Uh, dicendo che una build non dovrebbe chiamare un metodo HTTP sconfigge completamente l'esempio molto pratico di a FutureBuilder.
TheGeekZn,

6

Puoi impedire chiamate build indesiderate, usando in questo modo

1) Creare una classe Statefull figlio per una piccola parte dell'interfaccia utente

2) Utilizzare la libreria del provider , quindi utilizzandola è possibile interrompere la chiamata indesiderata del metodo di

compilazione

  • Dopo aver chiamato initState
  • Dopo aver chiamato didUpdateWidget
  • quando viene chiamato setState () .
  • quando la tastiera è aperta
  • quando l'orientamento dello schermo è cambiato
  • Il widget padre viene creato, quindi anche il widget figlio viene ricostruito

0

Anche Flutter ha ValueListenableBuilder<T> class . Ti consente di ricostruire solo alcuni dei widget necessari per il tuo scopo e di saltare i widget costosi.

puoi vedere i documenti qui Documenti flutter ValueListenableBuilder
o solo il seguente codice di esempio:

  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,
  ),
);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.