Les racines des arbres de widgets Flutter peuvent aller très profondément ...
Très profondes.
La nature des composants des widgets Flutter permet des conceptions d'applications très élégantes, modulaires et flexibles. Cependant, cela peut également entraîner beaucoup de code standard pour la transmission du contexte. Découvrez ce qui se passe lorsque nous voulons passer le accountId et scopeId de la page au widget deux niveaux ci-dessous:
class MyPage extends StatelessWidget {
final int accountId;
final int scopeId;
MyPage(this.accountId, this.scopeId);
Widget build(BuildContext context) {
return new MyWidget(accountId, scopeId);
}
}
class MyWidget extends StatelessWidget {
final int accountId;
final int scopeId;
MyWidget(this.accountId, this.scopeId);
Widget build(BuildContext context) {
// -
new MyOtherWidget(accountId, scopeId);
...
}
}
class MyOtherWidget extends StatelessWidget {
final int accountId;
final int scopeId;
MyOtherWidget(this.accountId, this.scopeId);
Widget build(BuildContext context) {
//
...
S'il n'est pas coché, ce modèle peut très facilement se glisser dans toute la base de code. Nous avons personnellement paramétré plus de 30 widgets de cette façon. Presque la moitié du temps de travail, le widget n'a reçu des paramètres que pour les transmettre plus loin, comme dans
MyWidget
l'exemple ci-dessus.
L'état de MyWidget est indépendant des paramètres, et pourtant il se reconstruit à chaque fois que les paramètres changent!
Bien sûr, il doit y avoir un meilleur moyen ...
Présentation d' InheritedWidget .
En un mot, il s'agit d'un type spécial de widget qui définit le contexte à la racine d'un sous-arbre. Il peut fournir efficacement ce contexte à chaque widget de ce sous-arbre. Le modèle d'accès doit sembler familier à un développeur Flutter:
final myInheritedWidget = MyInheritedWidget.of(context);
Ce contexte n'est qu'une classe Dart. Ainsi, il peut contenir tout ce que vous voulez y mettre. La plupart des contextes Flutter couramment utilisés, tels que
Style
ou MediaQuery
, ne sont rien de plus que des InheritedWidgets qui vivent au niveau MaterialApp
.
Si nous complétons l'exemple ci-dessus en utilisant InheritedWidget, voici ce que nous obtenons:
class MyInheritedWidget extends InheritedWidget {
final int accountId;
final int scopeId;
MyInheritedWidget(accountId, scopeId, child): super(child);
@override
bool updateShouldNotify(MyInheritedWidget old) =>
accountId != old.accountId || scopeId != old.scopeId;
}
class MyPage extends StatelessWidget {
final int accountId;
final int scopeId;
MyPage(this.accountId, this.scopeId);
Widget build(BuildContext context) {
return new MyInheritedWidget(
accountId,
scopeId,
const MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget();
Widget build(BuildContext context) {
// -
const MyOtherWidget();
...
}
}
class MyOtherWidget extends StatelessWidget {
const MyOtherWidget();
Widget build(BuildContext context) {
final myInheritedWidget = MyInheritedWidget.of(context);
print(myInheritedWidget.scopeId);
print(myInheritedWidget.accountId);
...
Il est important de noter:
- Les constructeurs sont désormais
const
ce qui rend ces widgets cachables; ce qui augmente la productivité. - Lorsque les paramètres sont mis à jour, un nouveau est créé
MyInheritedWidget
. Cependant, contrairement au premier exemple, le sous-arbre n'est pas reconstruit. Au lieu de cela, Flutter maintient un registre interne qui garde la trace des widgets qui y ont accédéInheritedWidget
et ne reconstruit que les widgets qui utilisent ce contexte. Dans cet exemple, c'est le casMyOtherWidget
. - Si l'arborescence est reconstruite pour une raison autre que des changements de paramètres, tels que des changements d'orientation, votre code peut toujours en créer un nouveau
InheritedWidget
. Cependant, puisque les paramètres restent les mêmes, les widgets de la sous-arborescence ne seront pas notifiés. C'est le but de la fonctionupdateShouldNotify
implémentée par le vôtreInheritedWidget
.
Enfin, parlons des bonnes pratiques.
InheritedWidget doit être petit
Les surcharger avec beaucoup de contexte conduit à la perte des deuxième et troisième avantages mentionnés ci-dessus, puisque Flutter ne peut pas déterminer quelle partie du contexte est mise à jour et quelle partie est utilisée par les widgets. Au lieu:
class MyAppContext {
int teamId;
String teamName;
int studentId;
String studentName;
int classId;
...
}
Je préfère faire:
class TeamContext {
int teamId;
String teamName;
}
class StudentContext {
int studentId;
String studentName;
}
class ClassContext {
int classId;
...
}
Utilisez const pour créer vos widgets
Sans const, il n'y a pas de reconstruction sélective du sous-arbre. Flutter crée une nouvelle instance de chaque widget de la sous-arborescence et l'invoque
build()
, gaspillant de précieux cycles, surtout si vos méthodes de construction sont suffisamment lourdes.
Gardez un œil sur la portée de votre InheritedWidget
-s
InheritedWidget
-y sont placés à la racine de l'arborescence des widgets. Ceci, en fait, détermine leur portée. Dans notre équipe, nous avons constaté qu'il était excessif de pouvoir déclarer le contexte n'importe où dans l' arborescence des widgets. Nous avons décidé de restreindre nos widgets contextuels pour accepter uniquement Scaffold
(ou ses dérivés) comme enfants. De cette façon, nous nous assurons que le contexte le plus granulaire peut être au niveau de la page, et nous obtenons deux étendues:
- Widgets au niveau de l'application tels que
MediaQuery
. Ils sont disponibles pour n'importe quel widget sur n'importe quelle page de votre application, car ils sont situés à la racine de l'arborescence des widgets de votre application. - Widgets au niveau de la page comme
MyInheritedWidget
l'exemple ci-dessus.
Vous devez choisir l'un ou l'autre selon l'endroit où le contexte s'applique.
Les widgets au niveau de la page ne peuvent pas traverser une bordure d'itinéraire
Cela semble évident. Cependant, cela a de sérieuses implications car la plupart des applications ont plus d'un niveau de navigation. Voici à quoi pourrait ressembler votre application:
> School App [App Context]
> Student [Student Context]
> Grades
> Bio
> Teacher [Teacher Context]
> Courses
> Bio
Voici ce que voit Flutter:
> School App [App Context]
> Student [Student Context]
> Student Grades
> Student Bio
> Teacher [Teacher Context]
> Teacher Courses
> Teacher Bio
Du point de vue de Flutter, il n'y a pas de hiérarchie de navigation. Chaque page (ou échafaudage) est un arbre de widgets associé à un widget d'application. Par conséquent, lorsque vous utilisez
Navigator.push
ces pages pour afficher, elles n'héritent pas du widget qui porte le contexte parent. Dans l'exemple ci-dessus, vous devrez transmettre explicitement le contexte Student
de la page Student à la page Student Bio.
Bien qu'il existe différentes façons de transmettre le contexte, je suggère de paramétrer les routes à l'ancienne (par exemple, le codage d'URL si vous utilisez des routes nommées). Cela garantit également que les pages peuvent être créées uniquement en fonction de l'itinéraire sans avoir à utiliser le contexte de leur page parent.
Bon codage!
Soyez à l'heure pour le cours!