StatefulWidget et cycle de vie des widgets
Introduction aux StatefulWidget
Section titled “Introduction aux StatefulWidget”Un StatefulWidget est un widget qui peut changer d’état au cours de sa durée de vie. Contrairement aux StatelessWidget, ils peuvent maintenir des données mutables et réagir aux interactions utilisateur.
Création d’un StatefulWidget
Section titled “Création d’un StatefulWidget”Voici comment créer un StatefulWidget basique avec un compteur :
import 'package:flutter/material.dart';
void main() { runApp(const MaterialApp(home: MainScreen()));}
class MainScreen extends StatefulWidget { const MainScreen({Key? key}) : super(key: key);
@override State<MainScreen> createState() => _MainScreenState();}
class _MainScreenState extends State<MainScreen> { int value = 0;
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), floatingActionButton: FloatingActionButton( onPressed: () => setState(() { value++; }), child: const Icon(Icons.add), ), body: Center( child: Text('$value'), ), ); }}Cycle de vie des widgets
Section titled “Cycle de vie des widgets”Méthodes du cycle de vie
Section titled “Méthodes du cycle de vie”Les StatefulWidget disposent de plusieurs méthodes de cycle de vie qui permettent de contrôler leur comportement :
class _MainScreenState extends State<MainScreen> with SingleTickerProviderStateMixin { final tabs = const [ Tab(text: 'Livres'), Tab(text: 'Films'), Tab(text: 'Disques'), ];
late TabController tabController;
@override void initState() { // Appelée une seule fois lors de la création du widget tabController = TabController( length: tabs.length, initialIndex: 0, vsync: this, ); super.initState(); }
@override void dispose() { // Appelée lors de la destruction du widget tabController.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( bottom: TabBar( controller: tabController, tabs: tabs, isScrollable: true, ), ), body: TabBarView( controller: tabController, children: const <Widget>[ Center(child: Text('Livres')), Center(child: Text('Films')), Center(child: Text('Musique')), ], ), ); }}Principales méthodes du cycle de vie
Section titled “Principales méthodes du cycle de vie”initState(): Appelée une seule fois lors de la création du widgetbuild(): Appelée chaque fois que le widget doit être reconstruitdispose(): Appelée lors de la destruction du widget pour libérer les ressources
Utilisation de setState
Section titled “Utilisation de setState”La méthode setState() est le mécanisme de base pour mettre à jour l’état d’un widget :
void _incrementCounter() { setState(() { _counter++; });}Bonnes pratiques avec setState
Section titled “Bonnes pratiques avec setState”- Appelez setState seulement quand nécessaire : Ne pas l’utiliser si aucune propriété visible ne change
- Gardez le code dans setState minimal : Seules les modifications d’état doivent y être placées
- Évitez les opérations coûteuses : Les calculs lourds doivent être faits avant l’appel à setState
Limitations de setState
Section titled “Limitations de setState”L’utilisation de setState présente plusieurs limitations importantes :
Problèmes de séparation des préoccupations
Section titled “Problèmes de séparation des préoccupations”- Séparation Vue/Logique difficile : Le code métier est mélangé avec l’interface utilisateur
- Testabilité réduite : Difficile de tester la logique indépendamment de l’interface
- Maintenance complexe : Les modifications deviennent difficiles à gérer
Problèmes de performance
Section titled “Problèmes de performance”- Reconstructions inutiles : setState déclenche un rebuild complet du widget
- Scalabilité limitée : Devient rapidement complexe dans de grandes applications
- Gestion d’état globale impossible : Difficile de partager l’état entre plusieurs widgets
Exemple des limitations
Section titled “Exemple des limitations”class _ProblematicScreenState extends State<ProblematicScreen> { bool isLoading = false; List<Item> items = []; String? errorMessage;
// Logique métier mélangée avec l'interface Future<void> loadItems() async { setState(() { isLoading = true; errorMessage = null; });
try { final response = await ApiService.getItems(); setState(() { items = response.items; isLoading = false; }); } catch (e) { setState(() { errorMessage = e.toString(); isLoading = false; }); } }
@override Widget build(BuildContext context) { // Interface utilisateur complexe difficile à tester return Scaffold(/* ... */); }}Solutions alternatives
Section titled “Solutions alternatives”L’utilisation de setState rend difficile la séparation Vue/Logique et par conséquent la testabilité.
De nombreux packages tentent de proposer une solution pour organiser les applications Flutter, en s’inspirant de différents patterns :
- Bloc : stream et réactivité
- Provider : notifiers et réactivité
- Mobx : Réactivité et ViewModel
- Redux : global store & unidirectional data flow
Conclusion
Section titled “Conclusion”Les StatefulWidget et setState sont parfaits pour :
- Applications simples avec peu d’état
- Composants isolés sans partage d’état
- Prototypage rapide et apprentissage
Pour des applications plus complexes, il est recommandé d’utiliser des solutions de gestion d’état plus avancées comme les Notifiers ou le package Provider.