web-dev-qa-db-fra.com

Comment faire face à la construction de widgets indésirables?

Pour diverses raisons, la méthode build de mes widgets est parfois appelée à nouveau.

Je sais que cela se produit parce qu'un parent a mis à jour. Mais cela provoque des effets indésirables. Une situation typique où cela pose des problèmes est la suivante: FutureBuilder:

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

Dans cet exemple, si la méthode build devait être appelée à nouveau, elle déclencherait une autre requête http. Ce qui est indésirable.

Compte tenu de cela, comment faire face à la construction non désirée? Y a-t-il un moyen d'empêcher l'appel à la construction?

49
Rémi Rousselet

La méthode build est conçue de telle sorte qu'elle soit pure/sans effets secondaires . En effet, de nombreux facteurs externes peuvent déclencher une nouvelle construction de widget, tels que:

  • Route pop/Push, pour les animations in/out
  • Redimensionnement de l'écran, généralement dû à l'apparence du clavier ou au changement d'orientation
  • Le widget parent recrée son enfant
  • Un widget InheritedWidget dont dépend le widget (motif Class.of(context)) change

Cela signifie que la méthode build devrait ne pas déclencher un appel http ou modifier un état quelconque.


Comment est-ce lié à la question?

Le problème que vous rencontrez est que votre méthode de génération a des effets secondaires/n'est pas pure, ce qui rend difficile un appel de génération superflue.

Au lieu d'empêcher l'appel de génération, vous devez rendre votre méthode de génération pure afin qu'elle puisse être appelée à tout moment sans impact.

Dans le cas de votre exemple, vous transformeriez votre widget en un StatefulWidget, puis extrayez cet appel HTTP vers le initState de votre State:

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

  • Il est également possible de créer un widget capable de reconstruire sans forcer ses enfants à construire aussi.

Lorsque l'instance d'un widget reste la même; Flutter délibérément ne reconstruira pas les enfants. Cela signifie que vous pouvez mettre en cache des parties de votre arborescence de widgets pour éviter les reconstructions inutiles.

Le moyen le plus simple consiste à utiliser les constructeurs Dart const:

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

Grâce à ce mot-clé const, l'instance de DecoratedBox restera la même, même si la construction a été appelée des centaines de fois.

Mais vous pouvez obtenir le même résultat manuellement:

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

Dans cet exemple, lorsque StreamBuilder est informé de nouvelles valeurs, subtree ne sera pas reconstruit, même si StreamBuilder/Column le fait. Cela se produit parce que, grâce à la fermeture, l'instance de MyWidget n'a pas changé.

Ce modèle est beaucoup utilisé dans les animations. Les utilisateurs typiques sont AnimatedBuilder et toutes les * Transition telles que AlignTransition.

Vous pouvez également stocker subtree dans un champ de votre classe, bien que cela soit moins recommandé car il empêche le rechargement à chaud.

87
Rémi Rousselet