Wie gehe ich mit unerwünschten Widget-Builds um?

142

Aus verschiedenen Gründen wird die buildMethode meiner Widgets manchmal erneut aufgerufen.

Ich weiß, dass es passiert, weil ein Elternteil aktualisiert hat. Dies führt jedoch zu unerwünschten Effekten. Eine typische Situation, in der Probleme auftreten, ist die folgende FutureBuilder:

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

Wenn in diesem Beispiel die Erstellungsmethode erneut aufgerufen wird, wird eine weitere http-Anforderung ausgelöst. Welches ist unerwünscht.

Wie geht man in Anbetracht dessen mit unerwünschten Builds um? Gibt es eine Möglichkeit, einen Build-Aufruf zu verhindern?

Rémi Rousselet
quelle
3
In der Providerdokumentation verlinken Sie hier mit der Aufschrift "Siehe diese Stackoverflow-Antwort, in der ausführlicher erläutert wird, warum die Verwendung des .value-Konstruktors zum Erstellen von Werten unerwünscht ist." Sie erwähnen den Wertekonstruktor hier oder in Ihrer Antwort jedoch nicht. Wolltest du woanders verlinken?
Suragch

Antworten:

223

Die Erstellungsmethode ist so konzipiert, dass sie rein / ohne Nebenwirkungen sein sollte . Dies liegt daran, dass viele externe Faktoren einen neuen Widget-Build auslösen können, z.

  • Route Pop / Push
  • Größenänderung des Bildschirms, normalerweise aufgrund des Erscheinungsbilds der Tastatur oder der Änderung der Ausrichtung
  • Das übergeordnete Widget hat sein untergeordnetes Element neu erstellt
  • Ein InheritedWidget, von dem das Widget abhängt ( Class.of(context)Muster-) Änderung

Dies bedeutet, dass die buildMethode keinen http-Aufruf auslösen oder einen Status ändern sollte .


Wie hängt das mit der Frage zusammen?

Das Problem, mit dem Sie konfrontiert sind, besteht darin, dass Ihre Erstellungsmethode Nebenwirkungen hat / nicht rein ist, was einen fremden Erstellungsaufruf problematisch macht.

Anstatt einen Build-Aufruf zu verhindern, sollten Sie Ihre Build-Methode rein machen, damit sie jederzeit ohne Auswirkungen aufgerufen werden kann.

Im Fall von Ihrem Beispiel, würden Sie das Widget in eine Transformation StatefulWidgetdann die , dass die HTTP - Aufruf extrahiert initStateIhre 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
      },
    );
  }
}

Ich weiß das schon. Ich bin hierher gekommen, weil ich Umbauten wirklich optimieren möchte

Es ist auch möglich, ein Widget neu zu erstellen, ohne dass seine Kinder ebenfalls zum Erstellen gezwungen werden.

Wenn die Instanz eines Widgets gleich bleibt; Flattern baut Kinder absichtlich nicht wieder auf. Dies bedeutet, dass Sie Teile Ihres Widget-Baums zwischenspeichern können, um unnötige Neuerstellungen zu vermeiden.

Der einfachste Weg ist die Verwendung von Dartkonstruktoren const:

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

Dank dieses constSchlüsselworts DecoratedBoxbleibt die Instanz von gleich, auch wenn der Build hunderte Male aufgerufen wurde.

Sie können jedoch das gleiche Ergebnis manuell erzielen:

@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 diesem Beispiel wird StreamBuilder, wenn er über neue Werte benachrichtigt subtreewird, auch dann nicht neu erstellt, wenn StreamBuilder / Column dies tut. Es passiert, weil sich dank der Schließung die Instanz von MyWidgetnicht geändert hat.

Dieses Muster wird häufig in Animationen verwendet. Typische Verwendungen sind AnimatedBuilderund alle Übergänge wie AlignTransition.

Sie können auch subtreein einem Feld Ihrer Klasse speichern , obwohl dies weniger empfohlen wird, da dadurch die Hot-Reload-Funktion unterbrochen wird.

Rémi Rousselet
quelle
2
Können Sie erklären, warum das Speichern subtreein einem Klassenfeld das Hot-Reload unterbricht?
mFeinstein
4
Ein Problem, mit dem ich zu StreamBuildertun habe, ist, dass sich der Bildschirm ändert, wenn die Tastatur angezeigt wird, sodass Routen neu erstellt werden müssen. Also StreamBuilderwird neu aufgebaut und ein neues StreamBuildererstellt und es abonniert das stream. Wenn a ein StreamBuilderabonniert stream, snapshot.connectionStatewird ConnectionState.waiting, wodurch mein Code a zurückgibt CircularProgressIndicator, und snapshot.connectionStateändert sich dann , wenn Daten vorhanden sind, und mein Code gibt ein anderes Widget zurück, wodurch der Bildschirm mit verschiedenen Dingen flackert.
mFeinstein
1
Ich entschied mich für a StatefulWidget, abonniere das streamon initState()und setze das currentWidgetmit, setState()wenn streamneue Daten gesendet werden, und übergebe es currentWidgetan die build()Methode. Gibt es eine bessere Lösung?
mFeinstein
1
Ich bin ein bisschen durcheinander. Sie beantworten Ihre eigene Frage, aber vom Inhalt her sieht es nicht so aus.
sgon00
8
Äh, zu sagen, dass ein Build keine HTTP-Methode aufrufen sollte, macht das sehr praktische Beispiel von a vollständig zunichte FutureBuilder.
TheGeekZn
6

Auf diese Weise können Sie unerwünschte Build-Aufrufe verhindern

1) Erstellen Sie eine untergeordnete Statefull-Klasse für einen einzelnen kleinen Teil der Benutzeroberfläche

2) Verwenden Sie Provider - Bibliothek, indem es so können Sie unerwünschte Build - Methode aufrufen stoppen

In diese Situation unten build Methodenaufruf

  • Nach dem Aufruf von initState
  • Nach dem Aufruf von didUpdateWidget
  • wenn setState () aufgerufen wird.
  • wenn die Tastatur geöffnet ist
  • wenn sich die Bildschirmausrichtung geändert hat
  • Das übergeordnete Widget wird erstellt, und das untergeordnete Widget wird ebenfalls neu erstellt
Sanjayrajsinh
quelle
0

Flattern hat auch ValueListenableBuilder<T> class . Sie können nur einige der für Ihren Zweck erforderlichen Widgets neu erstellen und die teuren Widgets überspringen.

Sie können die Dokumente hier sehen. ValueListenableBuilder flattert Dokumente
oder nur den folgenden Beispielcode:

  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
quelle