Question #214EasyFlutter Basics

What is flutter hooks ? and how it is differ from state managements ?

#flutter#state

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

yaml
dependencies:
  flutter_hooks: ^0.20.0

Basic Example

Without Hooks (Traditional):

dart
class 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:

dart
class 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

dart
class 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

dart
class 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

dart
class 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

dart
class 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

dart
class 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

dart
class 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

dart
class 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

AspectFlutter HooksState Management (Bloc/Provider/Riverpod)
PurposeWidget lifecycle & logicApp-wide state sharing
ScopeSingle widgetMultiple widgets/screens
State TypeLocal stateGlobal/shared state
ComplexitySimpleVaries (low to high)
BoilerplateMinimalVaries
Learning CurveEasyVaries
Use CaseComponent logicApp 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

dart
class 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