web-dev-qa-db-fra.com

Flutter: comment utiliser correctement un widget hérité?

Quelle est la bonne façon d'utiliser un InheritedWidget? Jusqu'à présent, j'ai compris que cela vous donnait la possibilité de propager des données dans l'arborescence des widgets. À l'extrême, si vous définissez RootWidget, il sera accessible à partir de tous les widgets de l'arbre sur tous les itinéraires, ce qui me convient très bien car je dois rendre mon modèle/modèle accessible pour mes widgets sans avoir à recourir à des globaux ou à des singletons.

MAIS InheritedWidget est immuable, comment puis-je le mettre à jour? Et plus important encore, comment mes widgets Stateful sont-ils déclenchés pour reconstruire leurs sous-arbres?

Malheureusement, la documentation est très peu claire et après discussion avec beaucoup de personnes, personne ne semble vraiment savoir quelle est la bonne façon de l’utiliser.

J'ajoute une citation de Brian Egan:

Oui, je le vois comme un moyen de propager des données dans l’arbre. Ce que je trouve déroutant, d'après la documentation de l'API:

"Les widgets hérités, lorsqu'ils sont référencés de cette manière, vont permettre au consommateur de se reconstruire lorsque le widget hérité lui-même changera d'état."

Quand j'ai lu ceci pour la première fois, j'ai pensé:

Je pourrais farcir des données dans InheritedWidget et les muter plus tard. Lorsque cette mutation se produit, tous les Widgets faisant référence à mon InheritedWidget seront reconstruits.

Pour muter l'état d'un InheritedWidget, vous devez l'envelopper dans un StatefulWidget. Ensuite, vous effectuez une mutation de l'état du StatefulWidget et transmettez ces données au InheritedWidget, qui les transmet à tous ses enfants. Cependant, dans ce cas, il semble reconstruire tout l’arborescence sous StatefulWidget, pas seulement les widgets faisant référence à InheritedWidget. Est-ce exact? Ou saura-t-il en quelque sorte ignorer les widgets qui référencent le InheritedWidget si updateShouldNotify renvoie false?

64
Thomas

Le problème vient de votre citation, qui est incorrecte.

Comme vous l'avez dit, les Whergets hérités sont, comme les autres, immuables. Par conséquent, ils ne mettent pas à jour . Ils sont créés à nouveau.

Le problème est le suivant: InheritedWidget n’est qu’un simple widget qui ne contient que des données . Il n'a aucune logique de mise à jour ou que ce soit. Mais, comme tous les autres widgets, il est associé à un Element. Et devine quoi? Cette chose est modifiable et flutter le réutilisera chaque fois que possible!

La citation corrigée serait:

InheritedWidget, lorsqu'il est référencé de cette manière, provoquera la reconstruction du consommateur lorsque InheritedWidget associé à un InheritedElement change.

On parle beaucoup de la façon dont les widgets/éléments/renderbox sont branchés ensemble . Mais en bref, ils ressemblent à ceci (à gauche, votre widget typique, au milieu, les "éléments" et à droite, les "boîtes de rendu"):

enter image description here

Le problème est le suivant: lorsque vous instanciez un nouveau widget; le flutter le comparera à l'ancien. Réutilisez c'est "Element", qui pointe vers une RenderBox. Et muter les propriétés de RenderBox.


Okey, mais comment cela répond-il à ma question?

Lors de l'instanciation d'un InheritedWidget, puis en appelant context.inheritedWidgetOfExactType (ou MyClass.of qui est fondamentalement identique); ce qui est supposé, c'est qu'il écoutera la Element associée à votre InheritedWidget. Et chaque fois que Element obtient un nouveau widget, il force l'actualisation de tous les widgets ayant appelé la méthode précédente.

En bref, lorsque vous remplacez une InheritedWidget existante par une nouvelle; le flutter verra qu'il a changé. Et notifiera les widgets liés d'une modification potentielle.

Si vous avez tout compris, vous devriez déjà avoir deviné la solution:

Enveloppez votre InheritedWidget dans un StatefulWidget qui créera un nouveau InheritedWidget à chaque fois que quelque chose changera!

Le résultat final dans le code réel serait:

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

Mais la création d'un nouvel InheritedWidget ne permettrait-elle pas de reconstruire tout l'arbre?

Non, ça ne va pas forcément. Comme votre nouveau InheritedWidget peut potentiellement avoir exactement le même enfant qu'auparavant. Et par exact, je veux dire le même cas. Les widgets qui ont la même instance qu'auparavant ne sont pas reconstruits.

Et dans la plupart des cas (ayant un tour hérité à la racine de votre application), le widget hérité est constant . Donc, pas besoin de reconstruire.

69
Rémi Rousselet

TL; DR

N'utilisez pas de calculs lourds dans la méthode updateShouldNotify et utilisez const au lieu de new lors de la création d'un widget


Tout d'abord, nous devons comprendre ce qu'est un objet Widget, Element et Render.

  1. Render Les objets sont ce qui est réellement rendu à l'écran. Ils sont mutables , contiennent la logique de dessin et de présentation. L'arbre de rendu est très similaire au modèle d'objet de document (DOM) du Web et vous pouvez regarder un objet de rendu en tant que nœud DOM dans cet arbre.
  2. Widget - est une description de ce qui devrait être rendu. Ils sont immuables et bon marché. Ainsi, si un widget répond à la question "quoi?" (Approche déclarative), l'objet Render répond à la question "comment?" (Approche impérative). Une analogie tirée du Web est un "DOM virtuel".
  3. Element/BuildContext - est un proxy entre Widget et Render objets. Il contient des informations sur la position d'un widget dans l'arborescence * et sur la mise à jour de l'objet Render lorsqu'un widget correspondant est modifié.

Nous sommes maintenant prêts à plonger dans InheritedWidget et la méthode de BuildContext inheritFromWidgetOfExactType .

À titre d'exemple, nous recommandons de considérer cet exemple tiré de la documentation de Flutter sur 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 - juste un widget qui implémente dans notre cas une méthode importante - updateShouldNotify . updateShouldNotify - une fonction qui accepte un paramètre oldWidget et renvoie une valeur booléenne: true ou false.

Comme tout widget, InheritedWidget a un objet Element correspondant. C'est InheritedElement . InheritedElement appelle updateShouldNotify sur le widget chaque fois que nous construisons un nouveau widget (appel setState sur un ancêtre). Lorsque updateShouldNotify renvoie true InheritedElement itère via des dépendances (?) et la méthode d'appel aChangementDépendances dessus.

Où InheritedElement obtient des dépendances ? Ici, nous devrions regarder la méthode inheritFromWidgetOfExactType .

inheritFromWidgetOfExactType - Cette méthode définie dans BuildContext et chaque élément implémente l'interface BuildContext (Element == BuildContext). Donc, chaque élément a cette méthode.

Regardons le code de inheritFromWidgetOfExactType:

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

Ici, nous essayons de trouver un ancêtre dans _ inheritedWidgets mappé par type. Si l'ancêtre est trouvé, nous appelons alors inheritFromElement .

Le code pour inheritFromElement :

  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
  1. Nous ajoutons ancêtre en tant que dépendance de l'élément en cours (_dependencies.add (ancêtre))
  2. Nous ajoutons l'élément en cours aux dépendances d'ancêtres (ancestor.updateDependencies (this, aspect))
  3. Nous retournons le widget d'ancêtre à la suite de inheritFromWidgetOfExactType (return ancestor.widget)

Nous savons maintenant où InheritedElement obtient ses dépendances.

Regardons maintenant la méthode didChangeDependencies . Chaque élément a cette méthode:

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

Comme nous pouvons le voir, cette méthode marque simplement un élément comme sale et cet élément devrait être reconstruit à la prochaine image. Reconstruire signifie que la méthode d'appel a été construite sur l'élément de widget correspondant.

Mais qu'en est-il "Toute la sous-arborescence reconstruit lorsque je reconstruis InheritedWidget?". Nous devons ici nous rappeler que les widgets sont immuables et que si vous créez un nouveau widget, Flutter reconstruira le sous-arbre. Comment pouvons-nous résoudre ce problème?

  1. Cachez les widgets à la main (manuellement)
  2. Utilisez const car const crée la seule instance of value/class
12
maksimr

De la docs :

[BuildContext.inheritFromWidgetOfExactType] obtient le widget le plus proche du type donné, qui doit être le type d'une sous-classe InheritedWidget concrète, et enregistre ce contexte de construction dans ce widget de sorte que ce widget change (ou qu'un nouveau widget de ce type soit introduit, ou le widget disparaît), ce contexte de construction est reconstruit afin d’obtenir de nouvelles valeurs à partir de ce widget.

Ceci est généralement appelé implicitement à partir de méthodes statiques of (), par exemple. Thème de.

Comme l'OP l'a noté, une instance InheritedWidget ne change pas ... mais peut être remplacée par une nouvelle instance située au même emplacement dans l'arborescence des widgets. Lorsque cela se produit, il est possible que les widgets enregistrés doivent être reconstruits. La méthode InheritedWidget.updateShouldNotify effectue cette détermination. (Voir: docs )

Alors, comment une instance peut-elle être remplacée? Une instance InheritedWidget peut être contenue par un StatefulWidget, qui peut remplacer une ancienne instance par une nouvelle instance.

2
kkurian