StatefulWidget : qu'est-ce que c'est, cycle de vie et principe de fonctionnement

Auteur : IT Sectr Publié le : 2026-07-01 Temps de lecture : 9 min

StatefulWidget est un widget Flutter avec un état mutable, permettant à l'interface utilisateur de réagir aux actions de l'utilisateur, aux événements asynchrones et aux flux de données. Selon la documentation officielle de Flutter (Flutter.dev, 2026), StatefulWidget est utilisé pour tous les éléments interactifs de l'application : formulaires de saisie, animations, cases à cocher, interrupteurs et écrans chargeant des données depuis le réseau. Contrairement à StatelessWidget, il crée un objet State séparé qui persiste pendant tout son cycle de vie et peut être reconstruit sans recréer le widget lui-même.

Points clés

  • StatefulWidget est un widget qui peut modifier son état pendant l'exécution, déclenchant la reconstruction de l'interface via setState
  • Cycle de vie — StatefulWidget passe par les étapes createState, initState, didChangeDependencies, build, didUpdateWidget, dispose
  • Objet State est un objet séparé qui stocke l'état et existe indépendamment du widget pendant toute sa durée de vie
  • setState est la seule façon légitime d'informer Flutter de la nécessité de reconstruire le widget après une modification des données
  • Performances — l'utilisation excessive de StatefulWidget augmente la consommation de mémoire et le temps de rendu

Qu'est-ce que StatefulWidget ?

StatefulWidget est une classe Flutter qui peut modifier son état en réponse aux actions de l'utilisateur, aux événements système ou aux opérations asynchrones. Contrairement à StatelessWidget, StatefulWidget n'est pas rendu directement — il crée un objet State qui se charge du rendu. Cette séparation en deux classes (Widget et State) permet à Flutter de reconstruire l'interface sans recréer le widget lui-même, offrant un avantage significatif en termes de performances lors des mises à jour fréquentes.

L'architecture de StatefulWidget suit le modèle de « séparation du mutable et de l'immuable » : le widget lui-même reste immuable (comme StatelessWidget), tandis que tout l'état mutable est stocké dans un objet State séparé. Cela permet à Flutter de réutiliser les widgets en les comparant par type et Key, tout en préservant l'état réel entre les reconstructions.

Selon Google (Flutter Architectural Overview, 2026), StatefulWidget est optimal pour les scénarios où l'état change plus d'une fois pendant la durée de vie du widget : champs de texte, animations, minuteries, flux de données, chargements asynchrones. Pour une initialisation unique, StatelessWidget est suffisant.

Quand StatefulWidget est-il nécessaire ?

StatefulWidget est obligatoire lorsque le widget doit répondre à des événements externes : clics sur des boutons, achèvement de requêtes HTTP, mises à jour de données de base de données, abonnements WebSocket. Il est également nécessaire pour les widgets avec animations, les champs de texte avec contrôleurs et les composants gérant le focus. Si un widget affiche uniquement des données et ne génère pas d'événements, utilisez StatelessWidget.

Structure interne

StatefulWidget se compose de deux classes : le StatefulWidget lui-même (léger, immuable) et State (lourd, mutable). Le framework crée State via la méthode createState(), appelée une fois lors de l'insertion dans l'arbre. State reçoit une référence au widget via la propriété widget et peut accéder à ses champs à tout moment du cycle de vie.

Cycle de vie de StatefulWidget

Le cycle de vie de StatefulWidget se compose de six étapes principales, chacune fournissant une méthode redéfinissable pour effectuer des tâches spécifiques. Comprendre ces étapes est essentiel pour une gestion appropriée des ressources et éviter les fuites de mémoire.

createState

createState est la première méthode du cycle de vie, appelée lorsque StatefulWidget est inséré dans l'arbre. Elle doit retourner une nouvelle instance de State associée à ce widget. Cette méthode est appelée exactement une fois pendant toute la durée de vie de l'élément. Il est important de ne pas effectuer d'opérations lourdes ici — createState doit être aussi léger que possible.

initState

initState est appelé immédiatement après la création de State, avant la première construction de l'interface. On y effectue : l'initialisation des contrôleurs (TextEditingController, AnimationController), l'abonnement aux flux de données (StreamSubscription), la configuration des minuteries et l'initialisation des champs. Selon la documentation Flutter (Flutter.dev, 2026), on ne peut pas appeler BuildContext.of() dans initState — l'arbre n'est pas encore complètement monté.

didChangeDependencies

didChangeDependencies est appelé après initState et à chaque fois que les dépendances InheritedWidget changent. C'est un endroit approprié pour appeler MediaQuery.of(context) ou s'abonner à Theme — des valeurs qui peuvent changer pendant l'exécution de l'application. Si un widget utilise InheritedWidget, la logique d'initialisation doit être ici, pas dans initState.

build et didUpdateWidget

build est la méthode principale qui retourne l'arbre de widgets. Elle est appelée après initState, après didChangeDependencies et après chaque setState. didUpdateWidget est appelé lorsque le parent se reconstruit et passe un StatefulWidget avec de nouveaux paramètres. On peut ici comparer les anciens et nouveaux champs du widget et, si nécessaire, mettre à jour l'état.

dispose

dispose est l'étape finale du cycle de vie. Toutes les ressources sont libérées ici : les abonnements aux flux sont résiliés, les contrôleurs sont supprimés, les minuteries sont annulées. Ne pas appeler dispose entraîne des fuites de mémoire. Après dispose, State est considéré comme mort — appeler setState à l'intérieur lève une exception.

Comment fonctionne StatefulWidget ?

Le mécanisme de fonctionnement de StatefulWidget repose sur le travail coordonné de trois entités : Widget (description légère), Element (couche intermédiaire) et State (stockage de données). Lorsque Flutter rencontre un StatefulWidget dans la description, il crée un StatefulElement, qui appelle createState et stocke une référence à l'objet State. Lorsque le parent se reconstruit, Flutter compare le nouveau widget avec l'Element actuel — si le type et la Key correspondent, l'Element est mis à jour et le State reste le même.

L'état est modifié uniquement via l'appel setState, qui informe le framework de la nécessité d'une reconstruction. Il est important de comprendre : setState ne modifie pas automatiquement l'état — il marque simplement le widget comme « sale ». Le développeur met à jour indépendamment les champs de State dans le callback passé à setState. Après la fin du callback, Flutter appelle build et met à jour l'interface.

Selon l'équipe Dart/Flutter (Dart Language Specification, 2026), cette séparation garantit que toutes les modifications d'état se produisent de manière synchrone avant l'appel de build, éliminant la situation où l'interface affiche des données partiellement mises à jour. C'est un mécanisme clé de cohérence de l'interface dans Flutter.

Exemples de code Dart

Regardons un StatefulWidget simple — un compteur de clics sur un bouton. Il démontre le modèle de base : création de State, initialisation d'un champ dans initState, modification via setState :

dart
class CounterScreen extends StatefulWidget {
  const CounterScreen({super.key});

  @override
  State<CounterScreen> createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Incrementer'),
        ),
      ],
    );
  }
}

Un exemple avec chargement asynchrone de données et gestion du cycle de vie. StatefulWidget charge des données depuis le réseau et affiche l'état de chargement :

dart
class UserProfilePage extends StatefulWidget {
  final String userId;
  const UserProfilePage({super.key, required this.userId});

  @override
  State<UserProfilePage> createState() => _UserProfilePageState();
}

class _UserProfilePageState extends State<UserProfilePage> {
  UserModel? _user;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadUser();
  }

  Future<void> _loadUser() async {
    final user = await UserService.fetchUser(widget.userId);
    setState(() {
      _user = user;
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) return const CircularProgressIndicator();
    return Text('Bonjour, ${_user!.name}');
  }
}

Dans le deuxième exemple, il est important de noter : initState lance une opération asynchrone, mais la méthode elle-même n'est pas asynchrone. L'asynchronisme est implémenté via async/await dans une méthode séparée _loadUser, qui met à jour l'état via setState après la fin de la requête. Cette approche garantit que le widget affiche correctement l'indicateur de chargement avant de recevoir les données.

StatefulWidget vs StatelessWidget

Le choix entre StatefulWidget et StatelessWidget ne concerne pas seulement la présence d'état. StatefulWidget fournit un cycle de vie complet avec les méthodes initState, didChangeDependencies, didUpdateWidget et dispose, nécessaires pour travailler avec les contrôleurs, les animations et les flux. StatelessWidget, en revanche, ne possède pas ces méthodes et est toujours plus léger pour le framework.

La recommandation de l'équipe Flutter (Flutter docs, 2026) est de minimiser le nombre de StatefulWidgets dans une application en remontant l'état dans l'arbre (State Hoisting) ou en utilisant des solutions de gestion d'état (Riverpod, Bloc, Provider). Chaque StatefulWidget crée un objet State qui vit jusqu'à ce que l'élément soit supprimé — plus ces widgets sont nombreux, plus la charge mémoire est élevée.

CritèreStatefulWidgetStatelessWidget
ÉtatMutableImmuable
Cycle de vie6 étapesBuild uniquement
Objet StateCréé séparémentNon requis
setStateDisponibleNon disponible
AbonnementsinitState/disposeNon supportés
Constructeur constLimitéTotalement supporté
Consommation mémoirePlus élevéePlus faible

Performances et optimisation

StatefulWidget nécessite plus de ressources que StatelessWidget en raison de la nécessité de créer et maintenir un objet State. Cependant, une utilisation correcte de StatefulWidget n'entraîne pas de problèmes de performances si quelques règles sont respectées. Premièrement, évitez l'imbrication profonde de StatefulWidget — chaque niveau ajoute une surcharge au parcours de l'arbre. Deuxièmement, divisez un StatefulWidget complexe en plusieurs widgets simples, chacun responsable de sa propre partie de l'état.

Selon la recherche sur les performances de Flutter (Flutter.dev, février 2026), la cause la plus fréquente de baisse de FPS est l'appel de setState dans un widget parent qui reconstruit tous les descendants, y compris les StatelessWidgets qui n'ont pas modifié leur affichage. La solution consiste à extraire la partie mutable de l'interface dans un StatefulWidget séparé afin que setState ne reconstruise que les widgets minimalement nécessaires.

Utiliser const dans State est une autre technique importante. Si les widgets enfants sont déclarés comme const, Flutter ne les reconstruira pas lors de l'appel de setState dans le parent. Cela réduit la charge sur le framework et diminue le temps de rendu des trames.

Évitez les appels fréquents à setState

Chaque appel à setState déclenche une reconstruction complète du widget. Si l'état change à haute fréquence (par exemple, animation ou flux de données), envisagez d'utiliser AnimatedBuilder, ValueListenableBuilder ou StreamBuilder au lieu d'appeler setState manuellement. Ces widgets optimisent la reconstruction, en mettant à jour uniquement la partie de l'interface qui a réellement changé.

Erreurs courantes

La première erreur courante avec StatefulWidget est d'appeler setState après dispose. Lorsqu'un widget est retiré de l'arbre, State est considéré comme mort, et tout appel à setState lève une exception « setState called after dispose ». Cela se produit le plus souvent lorsqu'une opération asynchrone se termine après la suppression du widget. La solution consiste à vérifier le drapeau mounted avant d'appeler setState ou à annuler les opérations asynchrones dans dispose.

La deuxième erreur est d'effectuer des calculs lourds dans la méthode build. Étant donné que build est appelé à chaque setState et à chaque reconstruction du parent, tous les calculs doivent être aussi légers que possible. Si une opération gourmande en ressources est nécessaire, déplacez-la dans un Isolate séparé ou mettez en cache le résultat dans un champ de State.

La troisième erreur est de ne pas appeler super.initState() et super.dispose(). En redéfinissant ces méthodes, le développeur doit appeler l'implémentation parente. Sans cela, le framework ne pourra pas gérer correctement l'état de l'Element, entraînant des bugs difficiles à traquer.

Recommandations pour éviter les erreurs

  • Vérifiez toujours mounted avant setState dans les callbacks asynchrones
  • N'oubliez pas d'appeler super.initState() et super.dispose()
  • N'effectuez pas de requêtes HTTP directement dans build — utilisez initState
  • Résiliez tous les abonnements dans dispose
  • Utilisez un nombre minimal de StatefulWidgets dans votre projet

Questions fréquentes

Quelle est la différence entre StatefulWidget et StatelessWidget ?

StatefulWidget peut modifier son état via setState, a un cycle de vie (initState, dispose) et crée un objet State séparé. StatelessWidget ne peut pas modifier l'état et n'a pas de méthodes de cycle de vie — il affiche simplement les données reçues.

Combien de fois createState est-il appelé ?

createState est appelé exactement une fois pour chaque instance de StatefulElement. Même si le parent se reconstruit plusieurs fois, tant que le type et la Key du widget ne changent pas, createState n'est pas appelé — l'objet State existant est utilisé.

Que se passe-t-il si dispose n'est pas appelé ?

Les ressources ne seront pas libérées : les contrôleurs continueront de fonctionner en arrière-plan, les abonnements aux flux resteront actifs, les minuteries ne seront pas annulées. Cela entraîne des fuites de mémoire et peut provoquer des appels à setState après dispose, ce qui lève une exception.

StatefulWidget peut-il être const ?

Oui, le constructeur de StatefulWidget peut être const. Cependant, cela n'offre pas le même avantage que pour StatelessWidget — l'objet State sera toujours créé lors de la première insertion. const n'affecte que le widget lui-même (l'enveloppe légère), pas le State.

À quoi sert la méthode didUpdateWidget ?

didUpdateWidget est appelé lorsque le parent passe un StatefulWidget avec de nouveaux paramètres. Cela est nécessaire pour synchroniser l'état avec les nouvelles données — par exemple, si userId dans les paramètres a changé, le profil du nouvel utilisateur doit être chargé.

Résumé

  • StatefulWidget est un widget à état mutable utilisant un objet State séparé pour stocker les données et gérer le cycle de vie
  • Cycle de vie se compose de createState, initState, didChangeDependencies, build, didUpdateWidget et dispose, chacun ayant son propre objectif
  • setState est la seule façon légitime d'informer le framework d'un changement d'état, après quoi build est automatiquement appelé
  • mounted est un drapeau qui doit être vérifié avant d'appeler setState dans les opérations asynchrones pour éviter une exception après dispose
  • Performances — StatefulWidget nécessite plus de ressources que StatelessWidget ; il est recommandé de minimiser leur nombre en remontant l'état dans des couches externes
  • Les widgets enfants const dans State aident à réduire la quantité de reconstruction lors de l'appel de setState, améliorant les performances
  • Choix correct — utilisez StatefulWidget uniquement lorsque le widget doit gérer des données mutables ou des opérations asynchrones

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi