What is flutter hooks ? and how it is differ from state managements ?
Answer
Flutter Hooks: Usage and Differences from State Management
Flutter Hooks is a package that provides a way to reuse stateful logic between widgets, inspired by React Hooks. It's fundamentally different from state management solutions.
What are Flutter Hooks?
Flutter Hooks allow you to extract and reuse widget logic without changing the widget hierarchy. They provide a simpler way to manage local state and side effects.
Installation
yamldependencies: flutter_hooks: ^0.20.0
Basic Example
Without Hooks (Traditional):
dartclass CounterWidget extends StatefulWidget { _CounterWidgetState createState() => _CounterWidgetState(); } class _CounterWidgetState extends State<CounterWidget> { int _count = 0; Widget build(BuildContext context) { return Column( children: [ Text('Count: $_count'), ElevatedButton( onPressed: () => setState(() => _count++), child: Text('Increment'), ), ], ); } }
With Hooks:
dartclass CounterWidget extends HookWidget { Widget build(BuildContext context) { final count = useState(0); return Column( children: [ Text('Count: ${count.value}'), ElevatedButton( onPressed: () => count.value++, child: Text('Increment'), ), ], ); } }
Common Hooks
1. useState - Local state management
dartclass StateExample extends HookWidget { Widget build(BuildContext context) { final counter = useState(0); final name = useState(''); final isLoading = useState(false); return Column( children: [ Text('Counter: ${counter.value}'), TextField( onChanged: (value) => name.value = value, ), if (isLoading.value) CircularProgressIndicator(), ], ); } }
2. useEffect - Side effects and lifecycle
dartclass EffectExample extends HookWidget { Widget build(BuildContext context) { final count = useState(0); // Runs on mount and when count changes useEffect(() { print('Count changed: ${count.value}'); // Cleanup function return () { print('Cleaning up'); }; }, [count.value]); // Runs only on mount useEffect(() { print('Component mounted'); return null; }, const []); return Text('Count: ${count.value}'); } }
3. useMemoized - Expensive computations
dartclass MemoExample extends HookWidget { Widget build(BuildContext context) { final count = useState(0); // Only recalculates when count changes final expensiveValue = useMemoized( () { print('Calculating expensive value'); return count.value * 1000; }, [count.value], ); return Column( children: [ Text('Count: ${count.value}'), Text('Expensive: $expensiveValue'), ElevatedButton( onPressed: () => count.value++, child: Text('Increment'), ), ], ); } }
4. useTextEditingController - Form controllers
dartclass FormExample extends HookWidget { Widget build(BuildContext context) { final nameController = useTextEditingController(); final emailController = useTextEditingController(); // Auto-disposed when widget is removed return Column( children: [ TextField(controller: nameController), TextField(controller: emailController), ElevatedButton( onPressed: () { print('Name: ${nameController.text}'); print('Email: ${emailController.text}'); }, child: Text('Submit'), ), ], ); } }
5. useFuture - Async operations
dartclass FutureExample extends HookWidget { Widget build(BuildContext context) { final userFuture = useFuture(fetchUser()); if (userFuture.connectionState == ConnectionState.waiting) { return CircularProgressIndicator(); } if (userFuture.hasError) { return Text('Error: ${userFuture.error}'); } return Text('User: ${userFuture.data?.name}'); } Future<User> fetchUser() async { await Future.delayed(Duration(seconds: 2)); return User(name: 'John Doe'); } }
6. useStream - Stream handling
dartclass StreamExample extends HookWidget { Widget build(BuildContext context) { final stream = useMemoized(() => Stream.periodic(Duration(seconds: 1), (count) => count) ); final snapshot = useStream(stream); return Text('Stream value: ${snapshot.data ?? 0}'); } }
7. useAnimationController - Animations
dartclass AnimationExample extends HookWidget { Widget build(BuildContext context) { final controller = useAnimationController( duration: Duration(seconds: 2), ); final animation = useAnimation( Tween<double>(begin: 0, end: 300).animate(controller), ); useEffect(() { controller.repeat(reverse: true); return null; }, []); return Container( width: animation, height: animation, color: Colors.blue, ); } }
Flutter Hooks vs State Management
| Aspect | Flutter Hooks | State Management (Bloc/Provider/Riverpod) |
|---|---|---|
| Purpose | Widget lifecycle & logic | App-wide state sharing |
| Scope | Single widget | Multiple widgets/screens |
| State Type | Local state | Global/shared state |
| Complexity | Simple | Varies (low to high) |
| Boilerplate | Minimal | Varies |
| Learning Curve | Easy | Varies |
| Use Case | Component logic | App architecture |
Key Differences
1. Scope of State
dart// Hooks - Local state class LocalCounter extends HookWidget { Widget build(BuildContext context) { final count = useState(0); // Only accessible here return Text('Count: ${count.value}'); } } // State Management - Global state final counterProvider = StateProvider((ref) => 0); class GlobalCounter extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final count = ref.watch(counterProvider); return Text('Count: $count'); // Accessible anywhere } }
2. Purpose
dart// Hooks - Manage widget lifecycle class TimerHook extends HookWidget { Widget build(BuildContext context) { useEffect(() { final timer = Timer.periodic(Duration(seconds: 1), (timer) { print('Tick'); }); return timer.cancel; // Auto cleanup }, []); return Text('Timer running'); } } // State Management - Business logic class TimerBloc extends Bloc<TimerEvent, TimerState> { // Complex business logic // Multiple events and states // Cross-feature coordination }
Combining Hooks with State Management
dart// Using hooks with Riverpod class CombinedExample extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { // Global state from Riverpod final user = ref.watch(userProvider); // Local state from hooks final isExpanded = useState(false); final controller = useTextEditingController(); // Side effects with hooks useEffect(() { print('User changed: ${user?.name}'); return null; }, [user]); return Column( children: [ Text('User: ${user?.name}'), TextField(controller: controller), Switch( value: isExpanded.value, onChanged: (value) => isExpanded.value = value, ), ], ); } }
Custom Hooks
dart// Create reusable custom hooks bool useDebounce(String value, Duration duration) { final debounced = useState(value); useEffect(() { final timer = Timer(duration, () { debounced.value = value; }); return timer.cancel; }, [value]); return debounced.value == value; } // Usage class SearchWidget extends HookWidget { Widget build(BuildContext context) { final search = useState(''); final shouldSearch = useDebounce(search.value, Duration(milliseconds: 500)); useEffect(() { if (shouldSearch) { performSearch(search.value); } return null; }, [shouldSearch]); return TextField( onChanged: (value) => search.value = value, ); } void performSearch(String query) { print('Searching for: $query'); } }
Advanced Hook Example
dartclass CompleteExample extends HookWidget { Widget build(BuildContext context) { // Multiple states final count = useState(0); final name = useState(''); final isLoading = useState(false); // Controllers (auto-disposed) final nameController = useTextEditingController(); final scrollController = useScrollController(); // Animation final animationController = useAnimationController( duration: Duration(milliseconds: 300), ); // Async operation final userFuture = useFuture( useMemoized(() => fetchUser(), [count.value]), ); // Side effects useEffect(() { // On mount print('Widget mounted'); // Cleanup return () => print('Widget disposed'); }, []); // Listen to scroll useEffect(() { void listener() { if (scrollController.position.pixels > 100) { animationController.forward(); } else { animationController.reverse(); } } scrollController.addListener(listener); return () => scrollController.removeListener(listener); }, []); return Scaffold( body: ListView( controller: scrollController, children: [ TextField(controller: nameController), Text('Count: ${count.value}'), if (userFuture.hasData) Text('User: ${userFuture.data?.name}'), ], ), ); } }
Best Practices
- Use hooks for local widget state
- Use state management for shared/global state
- Combine both for complex UIs
- Create custom hooks for reusable logic
- Keep hooks at the top of build method
- Don't call hooks conditionally
When to Use Each
Use Flutter Hooks for:
- Local component state
- Form controllers
- Animation controllers
- Lifecycle management
- Side effects within a widget
- Reusable widget logic
Use State Management for:
- App-wide state
- Business logic
- Data shared across screens
- Authentication state
- API data caching
- Complex state coordination
Important: Flutter Hooks and state management solutions serve different purposes and complement each other. Hooks simplify local widget logic, while state management handles app-wide state. Use both together for best results.
Learn more at Flutter Hooks Documentation