# Advanced Flutter Hooks

You can find the introduction to Flutter hooks here: [Flutter Hooks, everything to know about them](https://harishkunchala.com/flutter-hooks-everything-to-know-about-them)

Now today we are going to look at few advanced concepts using Flutter Hooks

## `useFuture`:

### **Example: Asynchronous Data Fetching with useFuture**

```dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

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

  Future<String> fetchData() async {
    await Future.delayed(const Duration(seconds: 2));
    return 'Fetched data from the server';
  }

  @override
  Widget build(BuildContext context) {
    final future = useMemoized(() => fetchData());
    final snapshot = useFuture(future);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Data Fetch using Hooks'),
      ),
      body: snapshot.connectionState == ConnectionState.waiting
          ? const CircularProgressIndicator()
          : snapshot.hasError
              ? Text('Error: ${snapshot.error}')
              : Text('Data : ${snapshot.data}'),
    );
  }
}
```

In here we have used two features of Hooks

1. **useMemoized**: Memorizes the future to avoid refetching data on every build. So it basically caches the future object.
    
2. **useFuture**: Manages the state of the future, providing connection states and results.
    

<mark>Remember: useFuture doesn't persist the Future in memory</mark> which is why `useMemoized` is needed to store the data.

You can find the code here: [hooks\_useFuture](https://github.com/khkred/learn_flutter_hooks/tree/hooks_useFuture)

## Combining Hooks

### Example: Create a Form that uses multiple Hooks

Our Form will use `useState`, `useTextEditingController` and `useEffect`

```dart
class CombinedHooksForm extends HookWidget {
  const CombinedHooksForm({super.key});

  @override
  Widget build(BuildContext context) {
    final _formKey = useMemoized(() => GlobalKey<FormState>());
    final _emailController = useTextEditingController();
    final _passwordController = useTextEditingController();
    final _isSubmitting = useState(false);

    useEffect(() {
      if (_isSubmitting.value) {
        // Let's do a Mock API call

        Future.delayed(const Duration(seconds: 2)).then((_) {
          _isSubmitting.value = false;
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
              content: Text('Form Submitted'),
            ),
          );
        });
      }
    }, [_isSubmitting.value]);

    void _submitForm() {
      if (_formKey.currentState!.validate()) {
        _isSubmitting.value = true;
      }
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('Combined Hooks Form'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _emailController,
                decoration: const InputDecoration(labelText: 'Email'),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter your email';
                  }
                  if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
                    return 'Please enter a valid email address';
                  }
                  return null;
                },
              ),
              TextFormField(
                controller: _passwordController,
                decoration: const InputDecoration(labelText: 'Password'),
                obscureText: true,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter your password';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 20),
              _isSubmitting.value
                  ? const CircularProgressIndicator()
                  : ElevatedButton(
                      onPressed: _submitForm,
                      child: const Text('Submit'),
                    ),
            ],
          ),
        ),
      ),
    );
  }
}
```

In the above class we have used the following:

1. **useTextEditingController**: Manages controllers for email and password fields.
    
2. **useState**: Tracks the submission state.
    
3. **useEffect**: Handles side effects when the form is submitted, such as showing a loading indicator and a success message.
    

You can find the source code for the above tutorial here: [combine\_hooks](https://github.com/khkred/learn_flutter_hooks/tree/combine_hooks)

## Custom Hooks:

### Example: Create a Timer that works from a Custom Hook

We are creating a timer that works from a custom hook

```dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

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

  @override
  Widget build(BuildContext context) {
    int useTimer() {
      final time = useState(0);

      useEffect(() {
        final timer =
            Timer.periodic(const Duration(seconds: 1), (timer) => time.value++);
        return () => timer.cancel();
      }, []);

      return time.value;
    }

    final time = useTimer();

    return Scaffold(
      appBar: AppBar(
        title: const Text('Timer using Custom Hook'),
      ),
      body: Center(
        child: Text('Timer: $time'),
      ),
    );
  }
}
```

In the above example we have used:

1. **useState**: Manages the timer state.
    
2. **useEffect**: Starts and cleans up the timer.
    
3. **Custom Hook**: `useTimer` encapsulates the logic for the timer, making it reusable.
    

You can find the source code for custom hooks here: [custom\_hooks](https://github.com/khkred/learn_flutter_hooks/tree/custom_hooks)

## Pagination:

### Example: Fetching and Displaying List Data with Pagination

We'll create an example that fetches paginated data from an API and displays it in a list.

```dart
class PaginatedListScreen extends HookWidget {
  const PaginatedListScreen({super.key});

  Future<List<String>> fetchPage(int page) async {
    await Future.delayed(
        const Duration(seconds: 2)); // Simulating network delay

    return List.generate(10, (index) => 'Item ${page * 10 + index + 1}');
  }

  @override
  Widget build(BuildContext context) {
    final _page = useState(0);
    final _items = useState<List<String>>([]);
    final _isFetching = useState(false);

    void _fetchNextPage() async {
      if (_isFetching.value) return;

      _isFetching.value = true;
      final newItems = await fetchPage(_page.value);

      // We are using a spread operator to combine both arrays into a new array
      _items.value = [..._items.value, ...newItems];

      // Once we get the value. We have to increase the page number
      _page.value++;

      //Finally after fetching is done. We have to set _isFetching to false
      _isFetching.value = false;
    }

    useEffect(() {
      _fetchNextPage();
    }, []);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Paginated List'),
      ),
      body: NotificationListener<ScrollNotification>(
        onNotification: (ScrollNotification scrollInfo) {
          if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
            _fetchNextPage();
          }
          return true;
        },
        child: ListView.builder(
          itemCount: _items.value.length,
          itemBuilder: (context, index) {
            if (index == _items.value.length) {
              return const Center(
                child: CircularProgressIndicator(),
              );
            }
            return ListTile(
              title: Text(_items.value[index]),
            );
          },
        ),
      ),
    );
  }
}
```

In the above example:

1. **useState**: Manages the current page, items, and fetching state.
    
2. **useEffect**: Fetches the first page of data when the component is mounted.
    
3. **NotificationListener**: Detects when the user scrolls to the bottom of the list and triggers the next page fetch.
    

You can find the source code for the above tutorial here: [paginated\_list](https://github.com/khkred/learn_flutter_hooks/tree/paginated_list)

These examples demonstrate how to use Flutter hooks for more advanced use cases. Experiment with these patterns and adapt them to your specific needs.

### Source Code:

As always you can find the source code here: [https://github.com/khkred/learn\_flutter\_hooks](https://github.com/khkred/learn_flutter_hooks)
