La gestion de l’état est l’un des aspects les plus importants du développement d’applications Flutter. Dans cet article, nous allons explorer les différentes approches disponibles et voir quand utiliser chacune d’elles.
Qu’est-ce que le State Management ?
Le state management (gestion d’état) fait référence à la façon dont vous gérez les données qui peuvent changer au fil du temps dans votre application. Ces données peuvent inclure :
- Les informations de l’utilisateur connecté
- Les données récupérées depuis une API
- L’état de l’interface utilisateur (formulaires, navigation, etc.)
- Les préférences de l’application
Une bonne gestion de l’état est essentielle pour créer des applications maintenables et performantes.
1. setState - La méthode de base
La méthode setState est la façon la plus simple de gérer l’état local d’un widget. Elle est parfaite pour les cas simples où l’état n’a pas besoin d’être partagé entre plusieurs widgets.
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Compteur: $_counter'),
ElevatedButton(
onPressed: _incrementCounter,
child: const Text('Incrémenter'),
),
],
);
}
}
Avantages de setState
- Simple à comprendre et à utiliser
- Pas de dépendances externes
- Parfait pour l’état local
Inconvénients
- Ne convient pas pour l’état partagé entre widgets
- Peut devenir complexe dans les grandes applications
2. Provider - La solution recommandée
Provider est la solution de gestion d’état recommandée par l’équipe Flutter. Elle utilise le pattern InheritedWidget de manière simplifiée.
Installation
Ajoutez Provider à votre pubspec.yaml :
dependencies:
flutter:
sdk: flutter
provider: ^6.1.1
Exemple avec ChangeNotifier
// Définition du modèle
class CartModel extends ChangeNotifier {
final List<Item> _items = [];
List<Item> get items => List.unmodifiable(_items);
int get totalPrice => _items.fold(0, (sum, item) => sum + item.price);
void add(Item item) {
_items.add(item);
notifyListeners(); // Notifie les widgets qui écoutent
}
void remove(Item item) {
_items.remove(item);
notifyListeners();
}
}
// Utilisation dans l'application
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: const MyApp(),
),
);
}
// Dans un widget
class CartScreen extends StatelessWidget {
const CartScreen({super.key});
@override
Widget build(BuildContext context) {
return Consumer<CartModel>(
builder: (context, cart, child) {
return Text('Total: ${cart.totalPrice} €');
},
);
}
}
3. Riverpod - Provider amélioré
Riverpod est une réécriture complète de Provider qui résout certaines de ses limitations.
// Définition d'un provider
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void decrement() => state--;
}
// Utilisation avec ConsumerWidget
class CounterScreen extends ConsumerWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(
child: Text('Count: $count'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}
4. BLoC - Business Logic Component
Le pattern BLoC sépare la logique métier de l’interface utilisateur en utilisant des streams.
// Événements
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
// BLoC
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<IncrementEvent>((event, emit) => emit(state + 1));
on<DecrementEvent>((event, emit) => emit(state - 1));
}
}
// Utilisation
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Column(
children: [
Text('$count'),
Row(
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: () => context.read<CounterBloc>().add(DecrementEvent()),
),
IconButton(
icon: const Icon(Icons.add),
onPressed: () => context.read<CounterBloc>().add(IncrementEvent()),
),
],
),
],
);
},
);
}
}
Tableau comparatif
| Solution | Complexité | Cas d’usage | Testabilité |
|---|---|---|---|
| setState | Faible | État local simple | Moyenne |
| Provider | Moyenne | Apps moyennes | Bonne |
| Riverpod | Moyenne | Apps moyennes/grandes | Excellente |
| BLoC | Élevée | Apps complexes | Excellente |
Conclusion
Le choix de la solution de gestion d’état dépend de plusieurs facteurs :
- La taille de votre application - Pour les petites apps,
setStateou Provider suffisent - La complexité de votre état - Plus l’état est complexe, plus BLoC ou Riverpod sont appropriés
- L’expérience de votre équipe - Choisissez une solution que votre équipe maîtrise
Mon conseil : commencez simple avec setState et Provider, puis migrez vers Riverpod ou BLoC si le besoin s’en fait sentir.
Cet article vous a été utile ? N’hésitez pas à le partager et à me suivre sur Twitter pour plus de contenu sur Flutter !
Vous avez aimé cet article ?