it-swarm.com.de

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

Aus verschiedenen Gründen wird manchmal die build -Methode meiner Widgets erneut aufgerufen.

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

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

In diesem Beispiel würde ein erneuter Aufruf der build -Methode eine weitere http-Anforderung auslösen. Welches ist unerwünscht.

In Anbetracht dessen, wie mit unerwünschten Builds umzugehen ist? Gibt es eine Möglichkeit, Build-Aufruf zu verhindern?

49
Rémi Rousselet

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

  • Pop/Push-Route für In/Out-Animationen
  • Die Größe des Bildschirms wird normalerweise aufgrund des Aussehens der Tastatur oder einer Änderung der Ausrichtung geändert
  • Das übergeordnete Widget hat sein untergeordnetes Element neu erstellt
  • Ein InheritedWidget, von dem das Widget abhängt, dass sich (Class.of(context) pattern) ändert

Dies bedeutet, dass die build -Methode not einen http-Aufruf auslösen oder einen beliebigen Status ändern sollte.


Wie hängt das mit der Frage zusammen?

Das Problem, mit dem Sie konfrontiert sind, ist, dass Ihre Erstellungsmethode Nebenwirkungen hat/nicht rein ist, was einen externen Erstellungsaufruf schwierig macht.

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

In Ihrem Beispiel würden Sie Ihr Widget in ein StatefulWidget umwandeln und dann diesen HTTP-Aufruf in das initState Ihres State extrahieren:

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
      },
    );
  }
}

  • Es ist auch möglich, ein Widget neu zu erstellen, ohne dass die untergeordneten Elemente dazu gezwungen werden müssen.

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

Am einfachsten ist es, Dart const -Konstruktoren zu verwenden:

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

Dank dieses Schlüsselworts const bleibt die Instanz von DecoratedBox unverändert, auch wenn 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 subtree nicht neu erstellt, wenn StreamBuilder über neue Werte informiert wird, auch wenn StreamBuilder/Column dies tun. Es kommt vor, weil sich dank der Schließung die Instanz von MyWidget nicht geändert hat.

Dieses Muster wird häufig in Animationen verwendet. Typische Benutzer sind AnimatedBuilder und alle * Übergänge wie AlignTransition.

Sie können subtree auch in einem Feld Ihrer Klasse speichern, obwohl dies weniger empfehlenswert ist, da das Hot-Reload unterbrochen wird.

87
Rémi Rousselet