Package Provider
Introduction au package Provider
Section titled “Introduction au package Provider”Provider présente plusieurs avantages significatifs pour la gestion d’état dans Flutter :
- Utilise les mécanismes natifs de Flutter :
InheritedWidget - Moins verbeux que Bloc
- Pas de génération de code
- Intégration naturelle avec l’écosystème Flutter
Mécanisme InheritedWidget
Section titled “Mécanisme InheritedWidget”Ce mécanisme permet à un widget de retrouver l’instance d’un widget “parent” dans l’arbre de widgets. Cela permet d’éviter de longues chaînes d’injection de paramètres.
Exemples d’utilisation native
Section titled “Exemples d’utilisation native”Flutter utilise déjà ce pattern dans de nombreuses APIs :
MediaQuery.of(context)- Informations sur l’écranNavigator.of(context)- NavigationTheme.of(context)- Thème de l’application
// Exemples d'utilisation nativefinal screenSize = MediaQuery.of(context).size;final theme = Theme.of(context);Navigator.of(context).push(/*...*/);Déclarer un Provider
Section titled “Déclarer un Provider”Les providers proposent deux constructeurs principaux selon votre cas d’usage :
Provider.value()
Section titled “Provider.value()”Pour utiliser un objet existant que vous avez déjà instancié :
class App extends StatelessWidget { const App({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { final user = User('joe@mail.com'); return Provider.value( value: user, child: const MaterialApp(home: MainScreen()), ); }}Provider()
Section titled “Provider()”Pour instancier un nouvel objet via une fonction create :
class App extends StatelessWidget { const App({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return Provider( create: (context) => UserService(), child: const MaterialApp(home: MainScreen()), ); }}Accéder à un Provider
Section titled “Accéder à un Provider”Deux syntaxes sont disponibles pour récupérer les objets depuis le provider :
Syntaxes disponibles
Section titled “Syntaxes disponibles”- Syntaxe classique :
Provider.of<AuthService>(context); - Extension de méthodes :
context.read<AuthService>();
class MainScreen extends StatelessWidget { const MainScreen({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { final user = context.read<User>(); return Scaffold( appBar: AppBar(title: Text(user.mail)), body: const MainView(), ); }}Différence entre read() et watch()
Section titled “Différence entre read() et watch()”class ExampleWidget extends StatelessWidget { @override Widget build(BuildContext context) { // read() : pour les actions (ne rebuild pas le widget) final userService = context.read<UserService>();
// watch() : pour l'écoute (rebuild le widget automatiquement) final user = context.watch<User>();
return Column( children: [ Text('Utilisateur: ${user.name}'), ElevatedButton( onPressed: () => userService.logout(), // Utilise read() child: Text('Déconnexion'), ), ], ); }}Types de Providers spécialisés
Section titled “Types de Providers spécialisés”Provider offre plusieurs types spécialisés selon le type de données que vous gérez :
ChangeNotifierProvider
Section titled “ChangeNotifierProvider”Pour les objets qui implémentent ChangeNotifier ou ValueNotifier :
class App extends StatelessWidget { const App({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (context) => TimeController(), child: const MaterialApp(home: MainScreen()), ); }}
class MainScreen extends StatelessWidget { const MainScreen({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { final timeController = context.watch<TimeController>(); return Scaffold( appBar: AppBar( title: Text(timeController.value.toIso8601String()), ), body: const MainView(), ); }}StreamProvider
Section titled “StreamProvider”Pour les flux de données (Stream) :
StreamProvider<List<Message>>( create: (context) => messageService.messagesStream, initialData: const [], child: MessageListWidget(),)FutureProvider
Section titled “FutureProvider”Pour les opérations asynchrones uniques :
FutureProvider<UserProfile>( create: (context) => userService.loadProfile(), initialData: UserProfile.empty(), child: ProfileWidget(),)Provider simple
Section titled “Provider simple”Pour les données statiques ou les services :
Provider<ApiService>( create: (context) => ApiService(apiKey: 'your-key'), child: MyApp(),)MultiProvider
Section titled “MultiProvider”Pour déclarer plusieurs Providers en une fois et éviter l’imbrication excessive :
MultiProvider( providers: [ Provider.value(value: userService), Provider.value(value: messageService), ChangeNotifierProvider(create: (context) => CartController()), StreamProvider<List<Notification>>( create: (context) => notificationService.stream, initialData: const [], ), ], child: MaterialApp(home: MainScreen()),);Exemple complet avec MultiProvider
Section titled “Exemple complet avec MultiProvider”class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ // Services Provider<ApiService>( create: (context) => ApiService(), ), Provider<AuthService>( create: (context) => AuthService(context.read<ApiService>()), ),
// Controllers avec état ChangeNotifierProvider<AuthController>( create: (context) => AuthController(context.read<AuthService>()), ), ChangeNotifierProvider<CartController>( create: (context) => CartController(), ),
// Streams StreamProvider<ConnectivityStatus>( create: (context) => ConnectivityService().statusStream, initialData: ConnectivityStatus.unknown, ), ], child: MaterialApp( home: HomeScreen(), ), ); }}ProxyProvider
Section titled “ProxyProvider”Pour définir des dépendances entre Providers, utilisez ProxyProvider :
MultiProvider( providers: [ ChangeNotifierProvider.value(value: authController), ProxyProvider<AuthController, FeedService>( update: (_, controller, service) => FeedService(FeedClient(controller.user?.token)), lazy: true, ), ], child: widget.child,);ProxyProvider avec plusieurs dépendances
Section titled “ProxyProvider avec plusieurs dépendances”ProxyProvider2<AuthController, ApiService, UserRepository>( update: (context, auth, api, previous) => UserRepository( apiService: api, authToken: auth.user?.token, ), child: MyWidget(),)Patterns de consommation
Section titled “Patterns de consommation”Consumer Widget
Section titled “Consumer Widget”Pour écouter les changements dans une partie spécifique du widget :
class CartScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Panier')), body: Consumer<CartController>( builder: (context, cart, child) { return ListView.builder( itemCount: cart.items.length, itemBuilder: (context, index) { final item = cart.items[index]; return ListTile( title: Text(item.name), subtitle: Text('\$${item.price}'), trailing: IconButton( icon: Icon(Icons.remove), onPressed: () => cart.removeItem(item.id), ), ); }, ); }, ), ); }}Selector pour optimiser les rebuilds
Section titled “Selector pour optimiser les rebuilds”Selector<CartController, int>( selector: (context, cart) => cart.itemCount, builder: (context, itemCount, child) { return Badge( label: Text('$itemCount'), child: Icon(Icons.shopping_cart), ); },)Gestion du cycle de vie
Section titled “Gestion du cycle de vie”Dispose automatique
Section titled “Dispose automatique”Provider gère automatiquement le cycle de vie des objets qu’il crée :
ChangeNotifierProvider( create: (context) => MyController(), // Provider appellera automatiquement dispose() sur MyController // quand le provider sera supprimé de l'arbre de widgets child: MyWidget(),)Contrôle manuel du dispose
Section titled “Contrôle manuel du dispose”ChangeNotifierProvider( create: (context) => MyController(), dispose: (context, controller) { // Logique de nettoyage personnalisée controller.cleanup(); controller.dispose(); }, child: MyWidget(),)Patterns avancés
Section titled “Patterns avancés”Provider conditionnel
Section titled “Provider conditionnel”class ConditionalProviderExample extends StatelessWidget { final bool useLocalData;
ConditionalProviderExample({required this.useLocalData});
@override Widget build(BuildContext context) { return Provider<DataService>( create: (context) => useLocalData ? LocalDataService() : RemoteDataService(), child: DataWidget(), ); }}Provider avec paramètres
Section titled “Provider avec paramètres”class ParameterizedProvider extends StatelessWidget { final String userId;
ParameterizedProvider({required this.userId});
@override Widget build(BuildContext context) { return Provider<UserProfileService>( create: (context) => UserProfileService(userId: userId), child: UserProfileWidget(), ); }}Bonnes pratiques
Section titled “Bonnes pratiques”1. Organisation des providers
Section titled “1. Organisation des providers”// ✅ Bon : hiérarchie logiqueMultiProvider( providers: [ // Services de base Provider<ApiService>(create: (context) => ApiService()),
// Services dépendants ProxyProvider<ApiService, AuthService>( update: (context, api, previous) => AuthService(api), ),
// Controllers ChangeNotifierProxyProvider<AuthService, UserController>( create: (context) => UserController(), update: (context, auth, controller) => controller..updateAuth(auth), ), ], child: MyApp(),)2. Éviter les providers inutiles
Section titled “2. Éviter les providers inutiles”// ❌ Mauvais : provider pour données statiques simplesProvider<String>.value( value: 'Hello World', child: MyWidget(),)
// ✅ Bon : passer directement en paramètreMyWidget(message: 'Hello World')3. Tests avec Provider
Section titled “3. Tests avec Provider”void main() { testWidgets('should display user name', (tester) async { final user = User(name: 'John Doe');
await tester.pumpWidget( Provider<User>.value( value: user, child: MaterialApp( home: UserWidget(), ), ), );
expect(find.text('John Doe'), findsOneWidget); });}Conclusion
Section titled “Conclusion”Le package Provider offre une solution robuste et flexible pour la gestion d’état dans Flutter. Il combine :
- Simplicité d’utilisation avec les extensions de méthodes
- Performance optimisée grâce aux mécanismes natifs Flutter
- Flexibilité avec de nombreux types de providers
- Testabilité excellente grâce à l’injection de dépendances
Provider est idéal pour les applications de moyenne à grande complexité où vous avez besoin de partager l’état entre plusieurs widgets tout en maintenant une architecture claire et testable.