Cos'è flutter_hooks

Chiunque abbia scritto applicazioni Flutter conosce bene la verbosità degli StatefulWidget: bisogna creare una classe separata per lo State, gestire manualmente initState, dispose, AnimationController e ricordarsi di liberare le risorse. La libreria flutter_hooks, ispirata ai React Hooks, propone un approccio più conciso e componibile per gestire lo stato locale e il ciclo di vita all'interno di un HookWidget.

In questo articolo vedremo come integrarla, gli hook più utili e quando conviene davvero adottarla.

Installazione

Aggiungi la dipendenza al tuo pubspec.yaml:

dependencies:
  flutter_hooks: ^0.20.5

Poi esegui flutter pub get.

Dal StatefulWidget all'HookWidget

Vediamo un classico contatore scritto con uno StatefulWidget:

class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => setState(() => _count++),
      child: Text('Valore: $_count'),
    );
  }
}

Lo stesso risultato con flutter_hooks:

class Counter extends HookWidget {
  const Counter({super.key});

  @override
  Widget build(BuildContext context) {
    final count = useState(0);

    return ElevatedButton(
      onPressed: () => count.value++,
      child: Text('Valore: ${count.value}'),
    );
  }
}

Niente classe State, niente setState: useState restituisce un ValueNotifier il cui valore, quando modificato, ricostruisce automaticamente il widget.

Gli hook più utili

useState

Come visto, mantiene un valore mutabile tra una build e l'altra.

useEffect

Esegue effetti collaterali (chiamate di rete, sottoscrizioni) e gestisce la pulizia, sostituendo initState e dispose:

useEffect(() {
  final subscription = stream.listen(print);
  // la funzione restituita viene chiamata al dispose
  return subscription.cancel;
}, const []); // [] = eseguito una sola volta

Il secondo parametro è la lista delle dipendenze: l'effetto viene rieseguito solo quando uno dei valori cambia.

useMemoized e useFuture

Utili per memorizzare risultati costosi o gestire future:

final future = useMemoized(() => api.fetchUser(id), [id]);
final snapshot = useFuture(future);

if (snapshot.connectionState == ConnectionState.waiting) {
  return const CircularProgressIndicator();
}
return Text(snapshot.data?.name ?? 'Nessun dato');

useTextEditingController e useAnimationController

Gli hook eliminano la necessità di dichiarare e fare il dispose manuale dei controller:

class LoginForm extends HookWidget {
  const LoginForm({super.key});

  @override
  Widget build(BuildContext context) {
    final emailController = useTextEditingController();
    final animation = useAnimationController(
      duration: const Duration(milliseconds: 300),
    );

    return TextField(controller: emailController);
  }
}

Il dispose viene gestito automaticamente dall'hook.

Creare un hook personalizzato

Uno dei vantaggi maggiori è la possibilità di estrarre logica riutilizzabile in hook custom:

ValueNotifier<int> useCounter({int initial = 0}) {
  final count = useState(initial);
  // logica aggiuntiva riutilizzabile
  return count;
}

Un hook custom è semplicemente una funzione che usa altri hook, favorendo la composizione.

Regole da rispettare

Gli hook seguono regole precise, analoghe a quelle di React:

  • Vanno chiamati sempre nello stesso ordine.
  • Non vanno mai chiamati dentro condizioni, cicli o funzioni annidate.
  • Vanno usati solo all'interno del metodo build di un HookWidget.

Violare queste regole porta a comportamenti imprevedibili, perché gli hook si basano sull'ordine di chiamata per associare lo stato.

Quando usarli

flutter_hooks brilla quando hai molti controller o logiche di ciclo di vita da gestire in un singolo widget, riducendo drasticamente il boilerplate. Si integra inoltre molto bene con Riverpod, tramite il pacchetto hooks_riverpod e il widget HookConsumerWidget.

Non è però una soluzione di gestione dello stato globale: per quello restano necessari pattern come Riverpod, Bloc o Provider. Gli hook gestiscono lo stato locale del widget.

Conclusione

flutter_hooks offre un modo elegante e componibile per ridurre la verbosità degli StatefulWidget, eliminando il rischio di dimenticare il dispose delle risorse. Pur richiedendo qualche regola da rispettare, l'aumento di leggibilità e riuso del codice ne fa uno strumento prezioso per progetti di medie e grandi dimensioni.