Article original : Learn Flutter Hooks – Common Hooks Explained with Code Examples
Les Flutter hooks sont des fonctions puissantes qui simplifient la gestion de l'état, la manipulation des effets secondaires et l'organisation du code dans les applications Flutter. Inspirés des hooks React, ils offrent une approche plus concise et modulaire par rapport aux modèles traditionnels StatefulWidget et setState.
À la fin de ce guide, vous comprendrez les hooks essentiels de Flutter, comment les utiliser efficacement, comment créer vos propres hooks personnalisés et les meilleures pratiques pour les utiliser dans des projets réels.
Table des matières
Prérequis
Avant de plonger dans les Flutter hooks, assurez-vous de disposer des éléments suivants :
Flutter SDK : Installé et configuré (Flutter 3.x ou supérieur recommandé). Vérifiez avec :
flutter --versionDart SDK : Inclus avec Flutter, assurez-vous qu'il est à jour.
IDE : Visual Studio Code, Android Studio ou IntelliJ avec les extensions Flutter.
Connaissances de base de Flutter : Familiarité avec les widgets,
StatelessWidget,StatefulWidgetet les bases de la gestion d'état.Dépendance du package : Le package
flutter_hooksinstallé en ajoutant ce qui suit au fichierpubspec.yaml:dependencies: flutter_hooks: ^0.21.3+1Ensuite, exécutez :
flutter pub get
Pourquoi utiliser les Flutter Hooks ?
Voici quelques-uns des avantages de l'utilisation des Flutter hooks :
Lisibilité et maintenabilité améliorées
Les hooks réduisent le code redondant (boilerplate) en intégrant la logique d'état et d'effets secondaires directement dans la méthode build du widget. Cela rend le code plus propre et plus facile à comprendre.Réutilisabilité
Les hooks peuvent être abstraits dans des hooks personnalisés. Par exemple, vous pourriez extraire une logique complexe (comme la récupération de données) dans une fonction réutilisable.Gestion d'état granulaire
Au lieu de gérer un seul objetStatepour un widget entier, les hooks vous permettent de gérer de petits morceaux d'état indépendants. C'est particulièrement utile pour les interfaces utilisateur complexes.Effets secondaires simplifiés
Les hooks tels queuseEffectoffrent un moyen élégant de gérer les tâches liées au cycle de vie, comme la récupération de données, les écouteurs (listeners) ou les abonnements.
Hooks Flutter courants
Parcourons les hooks les plus courants, avec des explications ligne par ligne.
Comment utiliser le hook useState dans Flutter
C'est le hook le plus simple et le plus utilisé. Il vous permet de déclarer et de gérer un état à l'intérieur d'un HookWidget.
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class CounterButton extends HookWidget {
@override
Widget build(BuildContext context) {
final counter = useState<int>(0); // Étape 1 : créer l'état avec une valeur initiale de 0
return ElevatedButton(
onPressed: () => counter.value++, // Étape 2 : mettre à jour l'état via counter.value
child: Text('Compte : ${counter.value}'), // Étape 3 : lire l'état
);
}
}
Explication
useState<int>(0)initialise l'état avec une valeur de0.counter.valuelit la valeur de l'état.La mise à jour de
counter.valuedéclenche une reconstruction (rebuild), tout commesetState.
Comment utiliser le hook useAnimationController dans Flutter
Gère les animations tout en gérant automatiquement le cycle de vie du contrôleur.
class AnimatedBox extends HookWidget {
@override
Widget build(BuildContext context) {
final controller = useAnimationController(
duration: const Duration(seconds: 1), // Étape 1 : définir la durée de l'animation
);
return FadeTransition(
opacity: controller, // Étape 2 : lier le contrôleur à l'animation
child: Container(width: 100, height: 100, color: Colors.blue),
);
}
}
Explication
Le hook crée un
AnimationControllerd'une durée d'une seconde.Le contrôleur est automatiquement libéré (
disposed) lorsque le widget est supprimé.Vous pouvez déclencher des animations avec
controller.forward()oucontroller.reverse().
Comment utiliser le hook useEffect dans Flutter
Gère les effets secondaires tels que la récupération de données ou la mise en place d'écouteurs.
class DataWidget extends HookWidget {
@override
Widget build(BuildContext context) {
useEffect(() {
fetchData(); // Étape 1 : exécuter l'effet secondaire
return () => cancelSubscription(); // Étape 2 : nettoyage optionnel
}, []); // Étape 3 : liste de dépendances
return Text('Chargement des données...');
}
}
Explication
Le callback s'exécute lors de la construction du widget.
La fonction de nettoyage s'exécute lorsque le widget est supprimé ou que les dépendances changent.
La liste de dépendances vide
[]signifie que l'effet ne s'exécute qu'une seule fois.
Comment utiliser le hook useMemoized dans Flutter
Met en cache les calculs coûteux et réutilise les résultats à moins que les dépendances ne changent.
final calculatedValue = useMemoized(() => calculateExpensiveValue(), []);
Explication
calculateExpensiveValue()s'exécute une fois et met le résultat en cache.Si des dépendances sont fournies, la fonction ne s'exécute à nouveau que lorsqu'elles changent.
Comment utiliser le hook useRef dans Flutter
Conserve une référence mutable à travers les reconstructions.
final textController = useRef(TextEditingController());
TextFormField(
controller: textController.value,
decoration: InputDecoration(labelText: 'Nom d\'utilisateur'),
);
Explication
useRefstocke un objet sans déclencher de reconstructions.Utile pour les contrôleurs, les focus nodes ou les valeurs mutables qui ne doivent pas être réinitialisées.
Comment utiliser le hook useCallback dans Flutter
Mémorise un callback pour éviter les reconstructions inutiles de widgets.
final onPressed = useCallback(() => print('Appuyé'), []);
Explication
Sans
useCallback, les fonctions peuvent être recréées à chaque reconstruction.Les callbacks mémorisés améliorent les performances lorsqu'ils sont transmis à des widgets comme
ListView.
Comment utiliser le hook useContext dans Flutter
Fournit un accès direct aux valeurs du BuildContext comme les thèmes ou les providers.
final theme = useContext();
Comment utiliser le hook useTextEditingController dans Flutter
Un raccourci pour créer des contrôleurs de texte.
final usernameController = useTextEditingController();
TextFormField(
controller: usernameController,
decoration: InputDecoration(labelText: 'Nom d\'utilisateur'),
);
Explication :
1. Qu'est-ce que useTextEditingController() ?
final usernameController = useTextEditingController();
Normalement dans Flutter, si vous voulez gérer la saisie de texte, vous créez un
TextEditingController.Avec un
StatefulWidgetclassique, vous feriez quelque chose comme :
late TextEditingController usernameController;
@override
void initState() {
super.initState();
usernameController = TextEditingController();
}
@override
void dispose() {
usernameController.dispose();
super.dispose();
}
Mais avec les Flutter Hooks, vous pouvez remplacer tout ce code redondant par :
final usernameController = useTextEditingController();Ce hook effectue automatiquement les actions suivantes :
Crée le contrôleur.
Le maintient en vie tant que le widget existe.
Le libère (dispose) lorsque le widget est détruit.
Vous n'avez donc plus besoin de gérer manuellement le cycle de vie.
2. Utilisation du contrôleur dans un TextFormField
TextFormField(
controller: usernameController,
decoration: InputDecoration(labelText: 'Nom d\'utilisateur'),
);
Ce
TextFormFieldest lié auusernameController.Tout ce que l'utilisateur tape dans le champ de saisie sera stocké dans
usernameController.text.Vous pouvez le lire ou le modifier à tout moment :
print(usernameController.text); // obtenir le texte saisi usernameController.text = "Anthony"; // définir une valeur par défaut
3. Fonctionnement global
useTextEditingController()fournit unTextEditingControllerprêt à l'emploi sans les tracas de l'initialisation et de la libération.Le
TextFormFieldutilise ce contrôleur pour gérer la saisie de l'utilisateur.C'est la méthode hooks pour gérer les champs de texte.
Résumé
useTextEditingController()→ Hook pour créer et libérer automatiquement unTextEditingController.TextFormField(controller: ...)→ Utilise ce contrôleur pour gérer et accéder au texte saisi dans le champ.Plus propre et plus sûr que la gestion manuelle dans un
StatefulWidget.
Comment créer un hook personnalisé dans Flutter
Vous pouvez encapsuler la logique dans des hooks réutilisables.
Future<String> useFetchData() {
final data = useState<String>('Chargement...');
useEffect(() {
Future.microtask(() async {
data.value = await fetchDataFromApi();
});
return null;
}, []);
return data.value;
}
Explication :
1. Signature de la fonction
Future<String> useFetchData()
À première vue, on dirait que cette fonction devrait renvoyer un Future<String>.
Mais en réalité, la fonction ne renvoie pas un Future, elle renvoie data.value, qui est une String.
La signature correcte devrait donc être :
String useFetchData()
Parce que vous renvoyez l'état actuel des données, pas un Future.
2. Configuration de l'état
final data = useState<String>('Chargement...');
Cela crée une variable d'état
dataavec une valeur initiale de"Chargement...".data.valuecontient la valeur réelle de la chaîne.La mise à jour de
data.valueprovoquera la reconstruction du widget.
3. Hook d'effet
useEffect(() {
Future.microtask(() async {
data.value = await fetchDataFromApi();
});
return null;
}, []);
useEffects'exécute une fois (car la liste de dépendances[]est vide).À l'intérieur, un
Future.microtaskplanifie une tâche asynchrone pour récupérer les données.Une fois l'appel API terminé,
data.valueest mis à jour avec la réponse defetchDataFromApi().La mise à jour de
data.valuedéclenche une reconstruction, de sorte que l'interface utilisateur affichera les nouvelles données au lieu de"Chargement...".
4. Valeur de retour
return data.value;
Cela renvoie la valeur de l'état actuel (
'Chargement...'au début, remplacée plus tard par les données récupérées).Lors de la première construction, vous obtiendrez
"Chargement...".Une fois l'appel API terminé, une reconstruction se produit et maintenant
useFetchData()renverra la chaîne récupérée.
5. Fonctionnement en pratique
Imaginez ce code de widget :
class MyWidget extends HookWidget {
@override
Widget build(BuildContext context) {
final result = useFetchData();
return Text(result);
}
}
Étape 1 → L'interface affiche "Chargement...".
Étape 2 → L'API est appelée en arrière-plan.
Étape 3 → Lorsque la réponse de l'API arrive, data.value est mis à jour.
Étape 4 → Le widget se reconstruit et maintenant Text(result) affiche les données récupérées.
Résumé
useStatecontient les données (Chargement...→ résultat récupéré).useEffects'exécute une fois pour déclencher la récupération asynchrone.Lorsque la récupération est terminée, l'état est mis à jour → le widget se reconstruit → l'interface affiche la nouvelle valeur.
La fonction devrait renvoyer une
String, pas unFuture<String>.
Hooks avancés
useListenable: Fonctionne avecValueNotifierouChangeNotifier.useDebounced: Temporise la saisie, utile pour les champs de recherche.usePreviousState(provenant de bibliothèques communautaires) : Garde la trace de la valeur précédente.
Démonstration : Exemple de compteur avec des hooks
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class Counter extends HookWidget {
@override
Widget build(BuildContext context) {
final count = useState<int>(0); // variable d'état initialisée à 0
useEffect(() {
print('Compte mis à jour : ${count.value}'); // log à chaque changement du compte
return null; // aucun nettoyage nécessaire
}, [count.value]); // dépendance : s'exécute à nouveau quand count change
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Vous avez cliqué ${count.value} fois', style: TextStyle(fontSize: 24)),
ElevatedButton(
onPressed: () => count.value++, // incrémenter l'état
child: Text('Incrémenter'),
),
],
);
}
}
Explication :
Ce code utilise le package flutter_hooks pour gérer l'état et le cycle de vie dans un style fonctionnel au lieu du classique StatefulWidget + setState. Décomposons-le étape par étape :
1. Définition de la classe
class Counter extends HookWidget {
@override
Widget build(BuildContext context) {
...
}
}
CounterétendHookWidgetau lieu deStatelessWidgetouStatefulWidget.HookWidgetvous permet d'utiliser des hooks (commeuseState,useEffect) directement dans la méthodebuildpour gérer l'état et les effets secondaires.
2. État avec useState
final count = useState<int>(0);
useStateest un hook qui crée un morceau d'état.Ici, il initialise
countà0.countn'est pas seulement unint, mais unValueNotifier<int>(ce qui signifie que vous pouvez lirecount.valueet le mettre à jour en lui assignant une nouvelle valeur viacount.value).
Initialement :count.value = 0.
3. Effet avec useEffect
useEffect(() {
print('Compte mis à jour : ${count.value}');
return null;
}, [count.value]);
useEffectest utilisé pour effectuer des effets secondaires chaque fois que les dépendances changent.Dans ce cas, il s'exécute chaque fois que
count.valuechange.Il affiche la valeur mise à jour dans la console à chaque changement du compteur.
Le second argument
[count.value]est la liste des dépendances (comme les React Hooks). Sicount.valuechange, cet effet s'exécute à nouveau.
4. Interface utilisateur (UI)
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Vous avez cliqué ${count.value} fois', style: TextStyle(fontSize: 24)),
ElevatedButton(
onPressed: () => count.value++, // incrémenter l'état
child: Text('Incrémenter'),
),
],
);
Une
Columnaffiche deux widgets :Un widget
Textmontrant le nombre de fois que le bouton a été cliqué.Un
ElevatedButtonqui incrémente le compteur lorsqu'on appuie dessus (count.value++).
Comme count est un ValueNotifier, la mise à jour de count.value déclenche automatiquement une reconstruction du widget.
5. Fonctionnement en pratique
L'application affiche : "Vous avez cliqué 0 fois" et un bouton "Incrémenter".
Lorsque vous appuyez sur le bouton :
count.valueaugmente de 1.Le widget se reconstruit, affichant le nouveau compte.
useEffects'exécute, affichantCompte mis à jour : Xdans la console.
Résumé :
Ce code est une application de compteur construite avec Flutter Hooks.
useStategère l'état du compteur.useEffectécoute les changements du compteur et exécute un effet secondaire (affichage dans la console).L'interface affiche le compte et un bouton pour l'incrémenter.
Bonnes pratiques
Utilisez correctement les listes de dépendances avec
useEffectetuseMemoized.Ne sur-utilisez pas les hooks : Parfois, un
StatefulWidgetest plus simple.Testez minutieusement, surtout lorsque des effets secondaires sont impliqués.
Extrayez la logique réutilisable dans des hooks personnalisés pour que vos widgets restent concentrés sur l'UI.
Hooks vs Stateful Widgets
Que sont les Stateful Widgets ?
Un StatefulWidget est un widget qui peut changer au fil du temps car il détient un état mutable.
Il est composé de deux classes :
StatefulWidget→ la configuration immuable.State<T>→ l'état mutable et la logique.
Comment ils fonctionnent
Lorsqu'un élément de l'état change, vous appelez
setState().Cela indique à Flutter de reconstruire l'arbre des widgets avec l'état mis à jour.
Exemple :
class Counter extends StatefulWidget {
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Vous avez cliqué $count fois'),
ElevatedButton(
onPressed: () => setState(() => count++),
child: Text('Incrémenter'),
),
],
);
}
}
Points clés
Adaptés pour les états d'interface simples (compteurs, interrupteurs, champs de formulaire).
Flutter gère le cycle de vie du widget (initialisation, reconstruction, suppression).
Vous gérez l'initialisation dans
initStateet le nettoyage dansdispose.
En résumé :
Les widgets à état (Stateful widgets) sont la méthode classique de gestion d'état dans Flutter. Ils sont intuitifs pour les débutants et parfaits pour des cas d'utilisation simples. Pour une logique d'état plus complexe ou réutilisable, les hooks (ou les bibliothèques de gestion d'état comme BLoC ou Riverpod) peuvent être plus propres et plus évolutifs.
Résumé :
Hooks : Plus propres, modulaires, réutilisables, excellents pour la gestion d'état avancée.
Stateful Widgets : Plus faciles pour les débutants et suffisants pour les états simples.