Gestion d’état avec Riverpod dans Flutter
Flutter Riverpod
-
Introduction
- Dans Flutter, la gestion des états désigne la façon dont vous gérez et maintenez les données (ou « état ») de votre application, notamment lorsqu’elles changent. Par exemple , lorsqu’un utilisateur interagit avec un bouton ou un champ de saisie, l’application peut avoir besoin d’actualiser l’écran. La gestion des états permet de suivre ces modifications et d’adapter l’interface utilisateur en conséquence.
- Parmi les différentes approches, deux des solutions de gestion d’état les plus populaires sont Provider et Riverpod .
- La gestion de l’état est essentielle dans Flutter car elle détermine comment les données sont partagées et mises à jour entre les widgets.
- Riverpod est un package moderne et puissant, conçu comme une amélioration de Provider.
- Avantages principaux :
- Sécurité du typage.
- Hot reload fiable.
- Plus de flexibilité que Provider.
- Compatible avec toutes les couches de l’application (UI, logique, API, etc.).
-
Installation de Riverpod
- Tout d’abord, vous devez ajouter
flutter_riverpod
comme dépendance dans votre fichier pubspec.yaml, vous pouvez le faire en exécutantflutter pub add flutter_riverpod
ou en ouvrant votre fichierpubspec.yaml
et en ajoutant - Vous pouvez maintenant l’importer comme ceci :
- Maintenant que vous l’avez installé, voyons comment vous pouvez l’utiliser
- Tout d’abord, nous devons encapsuler notre widget principal avec le widget ProviderScope (ou tout autre widget de l’arborescence pour une portée différente). Ce widget stockera tous les états des fournisseurs.
-
Les différents types de
Riverpod
- Les fournisseurs sont les éléments de base de la gestion de l’état dans Riverpod, chacun servant un objectif spécifique dans l’architecture de votre application.
- Bien que Riverpod propose actuellement huit types de fournisseurs, les modifications récentes de la version 2 ont simplifié ceux que vous devriez réellement utiliser dans vos projets.
- Examinons d’abord tous les fournisseurs disponibles sur Riverpod, puis concentrons-nous sur ceux recommandés pour les nouveaux projets :
-
Publié en version 1
-
Provider
- Provider : expose une valeur en lecture seule.
-
FutureProvider
- FutureProvider : pour les appels asynchrones (API, DB).
-
StreamProvider
- StreamProvider : pour les flux continus (WebSockets, Firebase).
-
StateProvider
- StateProvider : pour des états simples (compteurs, booléens, etc.).
-
StateNotifierProvider
- StateNotifierProvider : pour une logique métier plus complexe.
-
ChangeNotifierProvider
- ChangeNotifierProvider :
-
Publié en version 2 :
-
NotifierProvider
- Le NotifierProvider est utilisé pour gérer un état qui peut changer au cours du temps et nécessite une logique business.
-
AsyncNotifierProvider
- Le AsyncNotifierProvider est utilisé pour gérer des états asynchrones, comme des opérations de chargement de données, des appels API, etc.
-
Fournisseurs actuellement recommandés
- Basés sur la dernière version de Riverpod (v2), voici les fournisseurs activement maintenus et recommandés :
Provider
FutureProvider
StreamProvider
NotifierProvider
AsyncNotifierProvider
- Lorsque vous démarrez un nouveau projet Flutter avec Riverpod, privilégiez ces cinq fournisseurs. Ils couvrent tous les cas d’utilisation courants et représentent l’orientation future de Riverpod.
- Si vous maintenez ou héritez d’un projet utilisant des fournisseurs obsolètes, pas de panique ! Ils continueront de fonctionner, mais pensez à migrer progressivement vers les fournisseurs recommandés lors de vos cycles de maintenance réguliers.
-
ProviderScope : la racine de tous les fournisseurs
- Avant de plonger dans les fournisseurs, il y a une étape de configuration cruciale : envelopper votre application avec ProviderScope.
- Ce widget initialise Riverpod et gère l’état de tous les fournisseurs de votre application.
- Points clés à connaître sur ProviderScope:
- Doit être placé à la racine de votre arborescence de widgets
- Gère la création, la maintenance et la suppression des fournisseurs
- Gère la persistance de l’état lors des reconstructions de widgets
- Permet les remplacements de fournisseur pour les tests
- Nous aborderons ce sujet plus en détail dans la prochaine partie de cette série.
-
Utilisation dans l’UI
-
Utilisation dans l’UI
- Avec ConsumerWidget :
- Avec Consumer (plus localisé) :
-
Gestion asynchrone (FutureProvider & AsyncValue)
- Riverpod facilite la gestion des états loading, data, error :
-
Cas pratique : ToDo App avec Riverpod
- Modèle :
- Notifier :
- UI :
-
Riverpod vs Provider
- Provider :
- Plus ancien, moins flexible.
- Hot reload parfois problématique.
- Riverpod :
- Ne dépend pas de l’arbre de widgets.
- Plus robuste et plus adapté aux projets complexes.
-
Bonnes pratiques avec Riverpod
- Utiliser StateNotifier pour la logique métier complexe.
- Séparer les providers par fichiers (ex: auth_provider.dart, todo_provider.dart).
- Éviter de mélanger la logique métier dans l’UI.
-
Conclusion
- Riverpod est une solution moderne et puissante pour gérer l’état dans Flutter.
- Il remplace avantageusement Provider.
- Adapté aussi bien aux petits projets qu’aux grandes applications complexes.
- Combiné avec StateNotifier, il permet une architecture claire et maintenable.
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.0.0
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
ProviderScope(child: MyApp()),
);
}
final helloProvider = Provider((ref) => "Bonjour Riverpod !");
final userProvider = FutureProvider((ref) async {
await Future.delayed(Duration(seconds: 2));
return "Utilisateur chargé";
});
final timeProvider = StreamProvider((ref) {
return Stream.periodic(Duration(seconds: 1), (_) => DateTime.now());
});
final counterProvider = StateProvider((ref) => 0);
class CounterNotifier extends StateNotifier {
CounterNotifier() : super(0);
void increment() => state++;
}
final counterNotifierProvider = StateNotifierProvider(
(ref) => CounterNotifier(),
);
void main() {
runApp(
// Enable Riverpod for the entire app
ProviderScope(
child: MyApp(),
),
);
}
class HomePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider);
return Scaffold(
body: Center(child: Text("Compteur: $counter")),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Icon(Icons.add),
),
);
}
}
Consumer(
builder: (context, ref, child) {
final message = ref.watch(helloProvider);
return Text(message);
},
)
class UserPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider);
return userAsync.when(
data: (user) => Text("Bonjour $user"),
loading: () => CircularProgressIndicator(),
error: (err, _) => Text("Erreur: $err"),
);
}
}
class Todo {
final String title;
final bool completed;
Todo(this.title, {this.completed = false});
Todo copyWith({bool? completed}) =>
Todo(title, completed: completed ?? this.completed);
}
class TodoNotifier extends StateNotifier> {
TodoNotifier() : super([]);
void add(String title) {
state = [...state, Todo(title)];
}
void toggle(int index) {
state[index] = state[index].copyWith(completed: !state[index].completed);
state = [...state];
}
}
final todoProvider = StateNotifierProvider>(
(ref) => TodoNotifier(),
);
class TodoPage extends ConsumerWidget {
final TextEditingController controller = TextEditingController();
@override
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(todoProvider);
return Scaffold(
appBar: AppBar(title: Text("ToDo App avec Riverpod")),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return ListTile(
title: Text(todo.title),
trailing: Checkbox(
value: todo.completed,
onChanged: (_) => ref.read(todoProvider.notifier).toggle(index),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
ref.read(todoProvider.notifier).add(controller.text);
controller.clear();
},
child: Icon(Icons.add),
),
);
}
}