Gestion de l’état de vos variables Flutter avec setState


Avatar de Pierre Courtois

Comment faire pour transmettre des informations à travers vos différents widgets dans Flutter ? Dans ce guide, je vous explique comment faire cela, en utilisant setState.


Qu’est-ce que la gestion de l’état ?

Dans l’article précédent, nous avons vu comment transmettre des informations de manière descendante d’un widget parent à un widget enfant. C’est l’une des premières notions à comprendre en Flutter : les données circulent du haut vers le bas, du parent vers l’enfant. Mais dans la plupart des applications réelles, il est nécessaire de remonter ou de partager des informations entre plusieurs widgets. Par exemple, un widget enfant pourrait vouloir informer son parent d’un changement, ou des widgets de même niveau pourraient avoir besoin d’échanger des données. C’est là qu’intervient la gestion de l’état.

La gestion de l’état est un concept fondamental dans le développement d’applications interactives. Elle vous permet de stocker, de modifier et de partager des données entre vos widgets. Par exemple, lorsque vous voulez que votre application réagisse à une action de l’utilisateur (comme un clic sur un bouton ou un changement de valeur), vous devez savoir comment mettre à jour l’état et faire en sorte que l’interface utilisateur réagisse à ces changements.

Cela devient particulièrement important pour des cas comme :

  • Modifier un compteur chaque fois qu’un bouton est cliqué.
  • Changer l’apparence d’un bouton en fonction d’une action.
  • Mettre à jour un formulaire ou une interface en réponse à l’interaction d’un utilisateur.

Comment la gestion de l’état fonctionne-t-elle dans Flutter ?

Dans Flutter, l’une des manières les plus simples de gérer l’état est d’utiliser la méthode setState. Elle permet de notifier Flutter qu’un changement d’état a eu lieu, et que l’interface doit être mise à jour en conséquence.

Quand un changement d’état survient (par exemple, la valeur d’un compteur change), vous appelez setState pour informer Flutter que l’état a changé. Flutter va alors reconstruire le widget affecté pour afficher la nouvelle valeur.

Cela permet, par exemple, de faire remonter des informations d’un widget enfant vers son parent ou de faire circuler des données entre plusieurs widgets qui ne sont pas nécessairement dans une relation parent-enfant.

Le rôle de setState

Dans les fait, setState ne reconstruit pas tout l’arbre des widgets. Seul le widget concerné et ses enfants directs sont reconstruits. Cela permet de maintenir une bonne performance dans l’application. En d’autres termes, quand vous modifiez l’état avec setState, Flutter déclenche une mise à jour uniquement des parties nécessaires, ce qui optimise l’utilisation des ressources.

Cela permet, par exemple, de faire remonter des informations d’un widget enfant vers son parent ou de faire circuler des données entre plusieurs widgets qui ne sont pas nécessairement dans une relation parent-enfant.

Comment faire remonter une valeur d’un widget enfant vers le widget parent

Il est possible que vous ayez parfois besoin, non pas de transmettre une valeur du widget parent vers le widget enfant, mais l’inverse. Pour cela, vous pouvez utiliser une fonction de rappel (callback). Le parent va fournir une fonction au widget enfant, que celui-ci pourra utiliser pour envoyer l’information à son parent et le mettre à jour.

Par exemple, imaginons que ParentWidget affiche un compteur, et que ChildWidget contient un bouton pour l’augmenter. Chaque clic sur le bouton va faire remonter une valeur de ChildWidget à ParentWidget pour mettre à jour le compteur :

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: ParentWidget()));
}

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  int counter = 0; // Compteur à afficher
  // Méthode pour incrémenter le compteur

  void incrementCounter() {
    setState(() {
      counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            'Counter: $counter', // Affiche le compteur actuel
            style: TextStyle(fontSize: 24),
          ),
          SizedBox(height: 20),
          ChildWidget(
              onIncrement:
                  incrementCounter), // Passe la fonction au widget enfant
        ],
      )),
    );
  }
}

class ChildWidget extends StatelessWidget {
  final VoidCallback onIncrement; // Fonction de rappel pour le bouton

  const ChildWidget({required this.onIncrement});
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onIncrement, // Appelle la fonction de parent
      child: Text("Increment Counter"),
    );
  }
}

Explication

  1. Déclaration du compteur dans le widget parent : Dans ParentWidget, nous déclarons une variable counter pour stocker le nombre d’incréments. La fonction incrementCounter est utilisée pour augmenter ce compteur et appeler setState pour mettre à jour l’interface.
  2. Passage de la fonction au widget enfant : Dans ParentWidget, on passe incrementCounter au ChildWidget via le paramètre onIncrement.
  3. Appel de la fonction dans le widget enfant : Dans ChildWidget, le bouton utilise onIncrement comme fonction de rappel (callback). À chaque appui, onIncrement est appelé, ce qui déclenche incrementCounter dans ParentWidget et met à jour l’affichage du compteur.

Résultat

Avec ce modèle, le widget enfant ChildWidget peut déclencher une action dans le widget parent ParentWidget, et celui-ci peut afficher la mise à jour du compteur en temps réel.

Transmettre des valeurs entre plusieurs widgets

Maintenant que vous savez comment transmettre des informations d’un widget à un autre, voici comment faire pour transmettre des informations dans un système de trois widgets ou plus.

Imaginons un scénario où le ChildWidget modifie un compteur, et que ce changement doit être remonté au RootWidget (le parent), en passant par MiddleWidget (l’intermédiaire). Voici le processus que l’information va devoir suivre :

  1. RootWidget (le parent) définira une fonction de mise à jour (updateCounter).
  2. Cette fonction sera transmise à MiddleWidget, puis à ChildWidget.
  3. Lorsque le bouton dans ChildWidget est cliqué, il appellera la fonction de remontée pour informer le parent du changement de valeur.

Pour commencer, le parent crée une fonction updateCounter qui sera appelée par l’enfant, en passant via l’intermédiaire. Cette fonction récupère une valeur de type int, et va mettre à jour la valeur de _counter grâce à celle-ci. Le SetState va permettre de recharger le widget, avec la nouvelle valeur.

import 'package:flutter/material.dart';

class RootWidget extends StatefulWidget 
  @override
  _RootWidgetState createState() => _RootWidgetState();
}

class _RootWidgetState extends State<RootWidget> {
  int _counter = 0;
  // Fonction qui met à jour le compteur
  void updateCounter(int newValue) {
    setState(() {
      _counter = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Exemple de remontée de données")),
      body: Center(
        child: MiddleWidget(
          counter: _counter, // Passe la valeur actuelle
        onCounterChanged: updateCounter, // Passe la fonction de mise à jour
       ),
      ),
    );
  }
}

/*Le widget intermédiaire reçoit la fonction onCounterChanged depuis son constructeur avec la ligne final Function(int) onCounterChanged; et va pouvoir la transmettre à ChildWidget.
*/
class MiddleWidget extends StatelessWidget {
  final int counter;
  final Function(int) onCounterChanged; // Fonction pour mettre à jour le parent
 const MiddleWidget({
  Key? key,
   required this.counter,
    required this.onCounterChanged,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text(
          'Compteur actuel : $counter',
          style: TextStyle(fontSize: 24),
        ),
        SizedBox(height: 20),
        ChildWidget(onCounterChanged: onCounterChanged), // Passe la fonction au ChildWidget

      ],
    );
  }
}

Enfin, le widget enfant récupère lui aussi la fonction onCounterChanged grâce à son constructeur et va ainsi pouvoir l’utiliser pour remonter la valeur 42 au parent lorsque l’utilisateur appuie sur un bouton.

class ChildWidget extends StatelessWidget {
  final Function(int) onCounterChanged; // Fonction de remontée
  const ChildWidget({Key? key, required this.onCounterChanged}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        // Lorsqu'on appuie sur le bouton, on envoie la nouvelle valeur au parent
        onCounterChanged(42); // Remonte la valeur 42 au parent
      },
      child: Text("Changer la valeur du compteur à 42"),
    );
  }
}

Conclusion

À mesure que votre application grandit, gérer l’état à travers plusieurs widgets devient crucial. En utilisant setState de manière adéquate, vous pouvez transmettre des valeurs efficacement entre différents widgets, permettant ainsi à votre interface de rester dynamique et interactive. Toutefois, à mesure que votre application se complexifie, d’autres solutions comme Provider, Riverpod, ou Bloc peuvent devenir nécessaires pour gérer un état plus global et éviter de trop lourds passages de données entre des widgets multiples.

Avatar de Pierre Courtois