How to hook them up
The flutter_hooks package was prepared by Remi Rousselet, the creator of the popular Provider package.
Difference in the implementation of the sample Flutter application that counts clicks, using StatefulWidget and HookWidget:
Each widget build must call the same hooks in the same order which means that you cannot call them after conditionally exiting the build nor in an “if” statement.
Hooks must be used in the body of the build method.
Try to use the “use” prefix for your Hooks and call them at the start of the build. You will refer to their values afterwards.
Hooks solve the biggest StatefulWidget problem: it is hard to reuse logic from initState, dispose, didUpdateWidget and other widget lifecycle methods. For every similar widget, we must reimplement code from scratch in a non-elegant way. This can be partially solved with a mixins, but this solution has its limitations: they can be used once per class. Also, both mixins and the class share the same object that in some circumstances may generate some kind of unknown behavior.
Hooks solve this problem by moving all boilerplate logic to its own body that can be used only in the build method and be reused an infinite amount of times.
Sample use cases hooks:
use custom hooks
Simplest hook that creates a variable and subscribes to it.
The returned object is of the ValueNotifier<T> type, therefore it is not accessed directly through the variable, but through the variable.value. When the state changes, the widget rebuilds itself, as does the default setState.
Hook that caches the instance of a complex object.
This hook allows you to create an Object (such as a Stream or Future) the first time this builder function is invoked, without recreating it on each subsequent build – like initState but without it. That example also shows the use of the useStream hook. It serves to listen for updates to the Stream. This triggers a rebuild whenever a new value is emitted.
Hook useful for side effects and optionally canceling them.
This hook is called synchronously on every build, unless keys are specified. In this case, useEffect is called again only if any value inside the keys has changed. It also takes an effective callback and calls it synchronously. This effect can return a function that will be called when the widget is disposed of or when the effect is called again. Unless keys are specified, effect is called on every build. In another case it is called once on the first call or on a key’s change.
Use custom hooks
There are two ways to deliver custom hooks – as a function or as a custom class:
Function is the most common way to write hooks. Since the hooks are inherently composed, the function will be able to combine other hooks to create a custom one.
Example of using TabController as function:
Due to the fact that hooks can get more complicated, it is possible to convert them into a class. Hook as a class is very similar in looks to a State. Also, hook lifecycle methods are congruous to those known from the State.
It is good practice to wrap call of class hook to method like useTabController
Example of useTabController as class:
To handle the TabController, we must use a ticker provider which we will deliver by useSingleTickerProvider hook which takes ticker provider arguments: length and initialIndex as keys. That ensures that the provider will be recreated on any key change.
We use useMemoized to cache TabController to have it once in the widget lifetime. However, we pass tickerProvider as a key to ensure that the controller will recreate when tickerProvider changes.
Disposing TabController depends on the useEffect hook that disposes the controller when needed or when a new TabController has been provided via the parameter.