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?
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"):
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.
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.
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;
}
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?
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.