Comment gérer la construction de widgets indésirables?

143

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

Je sais que cela arrive parce qu'un parent a mis à jour. Mais cela provoque des effets indésirables. Une situation typique où cela pose des problèmes est lors de l'utilisation de FutureBuildercette façon:

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

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

Compte tenu de cela, comment gérer la construction indésirable? Y a-t-il un moyen d'empêcher l'appel de construction?

Rémi Rousselet
la source
4
Dans la documentation du fournisseur, vous créez un lien ici en disant "Voir cette réponse stackoverflow qui explique plus en détail pourquoi l'utilisation du constructeur .value pour créer des valeurs est indésirable." Cependant, vous ne mentionnez pas le constructeur de valeur ici ou dans votre réponse. Vouliez-vous créer un lien ailleurs?
Suragch

Réponses:

226

La méthode de construction est conçue de manière à être pure / sans effets secondaires . En effet, de nombreux facteurs externes peuvent déclencher une nouvelle construction de widget, tels que:

  • Route pop / push
  • Redimensionnement de l'écran, généralement en raison de l'apparence du clavier ou du changement d'orientation
  • Le widget parent a recréé son enfant
  • Un InheritedWidget dont le widget dépend du Class.of(context)changement (de modèle)

Cela signifie que la buildméthode ne doit pas déclencher un appel http ni modifier aucun état .


Comment cela est-il lié à la question?

Le problème auquel vous êtes confronté est que votre méthode de build a des effets secondaires / n'est pas pure, ce qui rend les appels de build superflus gênants.

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

Dans le cas de votre exemple, vous transformeriez votre widget en un StatefulWidgetpuis extrairiez cet appel HTTP vers le initStatede 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
      },
    );
  }
}

Je le sais déjà. Je suis venu ici parce que je veux vraiment optimiser les reconstructions

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

Lorsque l'instance d'un widget reste la même; Flutter ne reconstruira pas volontairement les enfants. Cela implique 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 des constconstructeurs de fléchettes :

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

Grâce à ce constmot - clé, l'instance de DecoratedBoxrestera la même même si build a été appelé 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 notifié des nouvelles valeurs, subtreeil ne sera pas reconstruit même si StreamBuilder / Column le fait. Cela arrive parce que, grâce à la fermeture, l'instance de MyWidgetn'a pas changé.

Ce modèle est beaucoup utilisé dans les animations. Les utilisations typiques sont AnimatedBuilderet toutes les transitions telles que AlignTransition.

Vous pouvez également stocker subtreedans un champ de votre classe, bien que moins recommandé car cela interrompt la fonction de rechargement à chaud.

Rémi Rousselet
la source
2
Pourriez-vous expliquer pourquoi le stockage subtreedans un champ de classe interrompt le rechargement à chaud?
mFeinstein
4
Un problème que j'ai avec StreamBuilderest que lorsque le clavier apparaît, l'écran change, donc les routes doivent être reconstruites. Ainsi StreamBuilderest reconstruit et un nouveau StreamBuilderest créé et il s'abonne au stream. Quand un StreamBuilders'abonne à un stream, snapshot.connectionStatedevient ConnectionState.waitingce qui fait que mon code renvoie un CircularProgressIndicator, puis snapshot.connectionStatechange lorsqu'il y a des données, et mon code renverra un widget différent, ce qui fait scintiller l'écran avec des éléments différents.
mFeinstein
1
J'ai décidé de faire un StatefulWidget, de m'abonner au streamon initState()et de définir le currentWidgetavec setState()comme streamenvoie de nouvelles données, en passant currentWidgetà la build()méthode. Y a-t-il une meilleure solution?
mFeinstein
1
Je suis un peu confus. Vous répondez à votre propre question, mais d'après le contenu, cela ne lui ressemble pas.
sgon00
8
Euh, dire qu'une construction ne devrait pas appeler une méthode HTTP va complètement à l'encontre de l'exemple très pratique d'un FutureBuilder.
TheGeekZn
6

Vous pouvez empêcher les appels de build indésirables, en utilisant ces méthodes

1) Créer une classe Statefull enfant pour une petite partie individuelle de l'interface utilisateur

2) Utilisez la bibliothèque Provider , donc en l'utilisant, vous pouvez arrêter l'appel de méthode de construction indésirable

Dans ces situations ci-dessous, appel de méthode de construction

  • Après avoir appelé initState
  • Après avoir appelé didUpdateWidget
  • lorsque setState () est appelé.
  • lorsque le clavier est ouvert
  • lorsque l'orientation de l'écran a changé
  • Le widget parent est construit puis le widget enfant est également reconstruit
Sanjayrajsinh
la source
0

Flutter a également ValueListenableBuilder<T> class . Il vous permet de reconstruire uniquement certains des widgets nécessaires à votre objectif et de sauter les widgets coûteux.

vous pouvez voir les documents ici ValueListenableBuilder flutter docs
ou juste l'exemple de code ci-dessous:

  return Scaffold(
  appBar: AppBar(
    title: Text(widget.title)
  ),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('You have pushed the button this many times:'),
        ValueListenableBuilder(
          builder: (BuildContext context, int value, Widget child) {
            // This builder will only get called when the _counter
            // is updated.
            return Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Text('$value'),
                child,
              ],
            );
          },
          valueListenable: _counter,
          // The child parameter is most helpful if the child is
          // expensive to build and does not depend on the value from
          // the notifier.
          child: goodJob,
        )
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
    child: Icon(Icons.plus_one),
    onPressed: () => _counter.value += 1,
  ),
);
Taba
la source