it-swarm.com.de

Flattern: Wie verwende ich ein geerbtes Widget richtig?

Wie verwende ich ein InheritedWidget richtig? Bisher habe ich verstanden, dass es Ihnen die Möglichkeit gibt, Daten im Widget-Baum zu verbreiten. Wenn Sie RootWidget angeben, können Sie auf alle Widgets im Baum auf allen Routen zugreifen. Dies ist in Ordnung, da ich mein ViewModel/Model irgendwie für meine Widgets zugänglich machen muss, ohne auf Globals oder Singletons zurückgreifen zu müssen.

ABER InheritedWidget ist unveränderlich. Wie kann ich es aktualisieren? Und was noch wichtiger ist, wie werden meine Stateful Widgets dazu veranlasst, ihre Teilbäume neu aufzubauen?

Leider ist die Dokumentation hier sehr unübersichtlich und nach vielen Diskussionen scheint niemand wirklich zu wissen, wie man sie richtig einsetzt.

Ich füge ein Zitat von Brian Egan hinzu:

Ja, ich sehe es als eine Möglichkeit, Daten im Baum zu verbreiten. Was ich aus den API-Dokumenten verwirrend finde:

"Wenn auf diese Weise auf geerbte Widgets verwiesen wird, wird der Consumer neu erstellt, wenn das geerbte Widget selbst den Status ändert."

Als ich das zum ersten Mal las, dachte ich:

Ich könnte einige Daten in InheritedWidget speichern und später ändern. Wenn diese Mutation auftritt, werden alle Widgets neu erstellt, die auf mein InheritedWidget verweisen. Was ich gefunden habe:

Um den Status eines InheritedWidget zu ändern, müssen Sie es in ein StatefulWidget umschließen. Anschließend ändern Sie den Status des StatefulWidget und geben diese Daten an das InheritedWidget weiter, das die Daten an alle untergeordneten Elemente weitergibt. In diesem Fall wird jedoch anscheinend die gesamte Struktur unter dem StatefulWidget neu erstellt, nicht nur die Widgets, die auf das InheritedWidget verweisen. Ist das korrekt? Oder wird es irgendwie wissen, wie man die Widgets überspringt, die auf InheritedWidget verweisen, wenn updateShouldNotify false zurückgibt?

64
Thomas

Das Problem ist auf Ihr falsches Angebot zurückzuführen.

Wie Sie sagten, sind InheritedWidgets wie andere Widgets unveränderlich. Deshalb update nicht. Sie werden neu erschaffen.

Die Sache ist: InheritedWidget ist nur ein einfaches Widget, das nur Daten enthält . Es gibt keine Logik für Updates oder was auch immer. Aber wie bei allen anderen Widgets ist es mit einem Element verknüpft. Und rate was? Dieses Ding ist wandelbar und Flatter wird es wann immer möglich wiederverwenden!

Das korrigierte Zitat wäre:

Wenn auf diese Weise auf InheritedWidget verwiesen wird, wird der Consumer neu erstellt, wenn InheritedWidget, das einem InheritedElement zugeordnet ist, geändert wird.

Es wird viel darüber gesprochen, wie Widgets/Elemente/Renderboxen zusammengefügt werden . Aber kurz gesagt, sie sind wie folgt (links ist das typische Widget, in der Mitte befinden sich 'Elemente' und rechts sind 'Renderfelder'):

enter image description here

Die Sache ist: Wenn Sie ein neues Widget instanziieren; flattern wird es mit dem alten vergleichen. Wiederverwenden ist es "Element", das auf eine RenderBox verweist. Und mutieren die RenderBox-Eigenschaften.


Okey, aber wie beantwortet das meine Frage?

Wenn Sie ein InheritedWidget instanziieren und dann context.inheritedWidgetOfExactType Aufrufen (oder MyClass.of, Was im Grunde dasselbe ist); Was impliziert ist, dass es auf das Element hört, das Ihrem InheritedWidget zugeordnet ist. Und wenn dieses Element ein neues Widget erhält, wird die Aktualisierung aller Widgets erzwungen, die die vorherige Methode aufgerufen haben.

Kurz gesagt, wenn Sie ein vorhandenes InheritedWidget durch ein neues ersetzen; Flattern wird sehen, dass es sich geändert hat. Und benachrichtigt die gebundenen Widgets über eine mögliche Änderung.

Wenn Sie alles verstanden haben, sollten Sie die Lösung bereits erraten haben:

Wickeln Sie Ihr InheritedWidget in ein StatefulWidget, das bei jeder Änderung ein neues InheritedWidget erstellt!

Das Endergebnis im eigentlichen Code wäre:

class MyInherited extends StatefulWidget {
  static MyInheritedData of(BuildContext context) =>
      context.inheritFromWidgetOfExactType(MyInheritedData) as MyInheritedData;

  const MyInherited({Key key, this.child}) : super(key: key);

  final Widget child;

  @override
  _MyInheritedState createState() => _MyInheritedState();
}

class _MyInheritedState extends State<MyInherited> {
  String myField;

  void onMyFieldChange(String newValue) {
    setState(() {
      myField = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MyInheritedData(
      myField: myField,
      onMyFieldChange: onMyFieldChange,
      child: widget.child,
    );
  }
}

class MyInheritedData extends InheritedWidget {
  final String myField;
  final ValueChanged<String> onMyFieldChange;

  MyInheritedData({
    Key key,
    this.myField,
    this.onMyFieldChange,
    Widget child,
  }) : super(key: key, child: child);

  @override
  bool updateShouldNotify(MyInheritedData oldWidget) {
    return oldWidget.myField != myField ||
        oldWidget.onMyFieldChange != onMyFieldChange;
  }
}

Aber würde ein neues InheritedWidget nicht den gesamten Baum neu erstellen?

Nein, das muss nicht sein. Da Ihr neues InheritedWidget möglicherweise genau dasselbe Kind wie zuvor haben kann. Und genau das meine ich auch. Widgets mit derselben Instanz wie zuvor werden nicht neu erstellt.

In den meisten Fällen (mit einem inheritedWidget im Stammverzeichnis Ihrer App) lautet das geerbte Widget Konstante. Also kein unnötiger Umbau.

69
Rémi Rousselet

TL; DR

Verwenden Sie in der updateShouldNotify -Methode keine umfangreichen Berechnungen und verwenden Sie const anstelle von neu beim Erstellen eines Widgets


Zunächst sollten wir verstehen, was ein Widget-, Element- und Render-Objekt ist.

  1. Rendern Objekte sind das, was tatsächlich auf dem Bildschirm gerendert wird. Sie sind veränderbar , enthalten die Zeichen- und Layoutlogik. Der Render-Baum ist dem Document Object Model (DOM) im Web sehr ähnlich, und Sie können ein Render-Objekt in diesem Baum als DOM-Knoten betrachten
  2. Widget - ist eine Beschreibung dessen, was gerendert werden soll. Sie sind unveränderlich und billig. Wenn also ein Widget die Frage "Was?" (Deklarativer Ansatz) beantwortet, beantwortet ein Render-Objekt die Frage "Wie?" (Imperativer Ansatz). Eine Analogie aus dem Web ist ein "Virtual DOM".
  3. Element/BuildContext - ist ein Proxy zwischen Widget und Objekte rendern . Es enthält Informationen zur Position eines Widgets in der Struktur * und zum Aktualisieren des Render-Objekts, wenn ein entsprechendes Widget geändert wird.

Jetzt können wir uns mit InheritedWidget und der BuildContext-Methode inheritFromWidgetOfExactType befassen.

Als Beispiel empfehle ich dieses Beispiel aus Flutters Dokumentation zu InheritedWidget:

class FrogColor extends InheritedWidget {
  const FrogColor({
    Key key,
    @required this.color,
    @required Widget child,
  })  : assert(color != null),
        assert(child != null),
        super(key: key, child: child);

  final Color color;

  static FrogColor of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(FrogColor);
  }

  @override
  bool updateShouldNotify(FrogColor old) {
    return color != old.color;
  }
}

InheritedWidget - nur ein Widget, das in unserem Fall eine wichtige Methode implementiert - updateShouldNotify . updateShouldNotify - eine Funktion, die einen Parameter oldWidget akzeptiert und einen booleschen Wert zurückgibt: true oder false.

Wie jedes Widget verfügt auch InheritedWidget über ein entsprechendes Element-Objekt. Es ist InheritedElement . InheritedElement ruft updateShouldNotify für das Widget auf, wenn wir ein neues Widget erstellen (ruft setState für einen Vorfahren auf). Wenn updateShouldNotify true InheritedElement zurückgibt, werden Abhängigkeiten durchlaufen. (?) und call method didChangeDependencies drauf.

Woher bekommt InheritedElement Abhängigkeiten ? Hier sollten wir uns die inheritFromWidgetOfExactType -Methode ansehen.

inheritFromWidgetOfExactType - Diese in BuildContext definierte Methode und jedes Element implementiert die BuildContext-Schnittstelle (Element == BuildContext). Also hat jedes Element diese Methode.

Schauen wir uns den Code von inheritFromWidgetOfExactType an:

final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
  assert(ancestor is InheritedElement);
  return inheritFromElement(ancestor, aspect: aspect);
}

Hier versuchen wir, einen Vorfahren in _ inheritedWidgets zu finden, der nach Typ zugeordnet ist. Wenn der Vorfahr gefunden wird, rufen wir inheritFromElement auf.

Der Code für inheritFromElement :

  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
  1. Wir fügen einen Vorfahren als Abhängigkeit des aktuellen Elements hinzu (_dependencies.add (ancestor))
  2. Wir fügen das aktuelle Element den Abhängigkeiten des Vorfahren hinzu (ancestor.updateDependencies (this, aspect))
  3. Wir geben das Widget des Vorfahren als Ergebnis von inheritFromWidgetOfExactType zurück (return ancestor.widget)

Jetzt wissen wir also, woher InheritedElement seine Abhängigkeiten bezieht.

Schauen wir uns nun die didChangeDependencies -Methode an. Jedes Element hat diese Methode:

  void didChangeDependencies() {
    assert(_active); // otherwise markNeedsBuild is a no-op
    assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
    markNeedsBuild();
  }

Wie wir sehen können, markiert diese Methode ein Element nur als dirty und dieses Element sollte im nächsten Frame neu erstellt werden. Rebuild bedeutet Aufrufmethode Build auf dem entsprechenden Widget-Element.

Aber was ist mit "Neuerstellung des gesamten Unterbaums beim Neuerstellen von InheritedWidget"? Hier sollten wir daran denken, dass Widgets unveränderlich sind und wenn Sie ein neues Widget erstellen, wird Flutter den Unterbaum neu erstellen. Wie können wir das beheben?

  1. Widgets manuell zwischenspeichern
  2. Verwenden Sie const , da const nur eine Instanz erstellen von value/class
12
maksimr

Aus dem docs :

[BuildContext.inheritFromWidgetOfExactType] ruft das nächste Widget des angegebenen Typs ab, das der Typ einer konkreten InheritedWidget-Unterklasse sein muss, und registriert diesen Erstellungskontext mit diesem Widget, sodass bei Änderung dieses Widgets (oder Einführung eines neuen Widgets dieses Typs) Wenn das Widget nicht mehr angezeigt wird, wird dieser Erstellungskontext neu erstellt, damit neue Werte von diesem Widget abgerufen werden können.

Dies wird typischerweise implizit von () statischen Methoden aufgerufen, z.B. Theme.of.

Wie das OP feststellte, ändert sich eine InheritedWidget -Instanz nicht ... sie kann jedoch durch eine neue Instanz an derselben Stelle im Widget-Baum ersetzt werden. In diesem Fall müssen die registrierten Widgets möglicherweise neu erstellt werden. Das InheritedWidget.updateShouldNotify Methode trifft diese Bestimmung. (Siehe: docs )

Wie kann eine Instanz ersetzt werden? Eine InheritedWidget -Instanz kann in einer StatefulWidget enthalten sein, die eine alte Instanz durch eine neue Instanz ersetzen kann.

2
kkurian