Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/rrousselgit/flutter_hooks
React hooks for Flutter. Hooks are a new kind of object that manages a Widget life-cycles. They are used to increase code sharing between widgets and as a complete replacement for StatefulWidget.
https://github.com/rrousselgit/flutter_hooks
code-reuse dart flutter hacktoberfest hook widget
Last synced: 7 days ago
JSON representation
React hooks for Flutter. Hooks are a new kind of object that manages a Widget life-cycles. They are used to increase code sharing between widgets and as a complete replacement for StatefulWidget.
- Host: GitHub
- URL: https://github.com/rrousselgit/flutter_hooks
- Owner: rrousselGit
- License: mit
- Created: 2018-11-30T18:26:39.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2024-08-04T19:43:47.000Z (3 months ago)
- Last Synced: 2024-10-29T09:06:50.626Z (7 days ago)
- Topics: code-reuse, dart, flutter, hacktoberfest, hook, widget
- Language: Dart
- Homepage:
- Size: 632 KB
- Stars: 3,138
- Watchers: 30
- Forks: 179
- Open Issues: 26
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
[English](https://github.com/rrousselGit/flutter_hooks/blob/master/README.md) | [Português](https://github.com/rrousselGit/flutter_hooks/blob/master/packages/flutter_hooks/resources/translations/pt_br/README.md) | [한국어](https://github.com/rrousselGit/flutter_hooks/blob/master/packages/flutter_hooks/resources/translations/ko_kr/README.md) | [简体中文](https://github.com/rrousselGit/flutter_hooks/blob/master/packages/flutter_hooks/resources/translations/zh_cn/README.md)
[![Build](https://github.com/rrousselGit/flutter_hooks/workflows/Build/badge.svg)](https://github.com/rrousselGit/flutter_hooks/actions?query=workflow%3ABuild) [![codecov](https://codecov.io/gh/rrousselGit/flutter_hooks/branch/master/graph/badge.svg)](https://codecov.io/gh/rrousselGit/flutter_hooks) [![pub package](https://img.shields.io/pub/v/flutter_hooks.svg)](https://pub.dev/packages/flutter_hooks) [![pub package](https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square)](https://github.com/Solido/awesome-flutter)
# Flutter Hooks
A Flutter implementation of React hooks: https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889
Hooks are a new kind of object that manage the life-cycle of a `Widget`. They exist
for one reason: increase the code-sharing _between_ widgets by removing duplicates.## Motivation
`StatefulWidget` suffers from a big problem: it is very difficult to reuse the
logic of say `initState` or `dispose`. An obvious example is `AnimationController`:```dart
class Example extends StatefulWidget {
const Example({super.key, required this.duration});final Duration duration;
@override
_ExampleState createState() => _ExampleState();
}class _ExampleState extends State with SingleTickerProviderStateMixin {
late final AnimationController _controller;@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: widget.duration);
}@override
void didUpdateWidget(Example oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.duration != oldWidget.duration) {
_controller.duration = widget.duration;
}
}@override
void dispose() {
_controller.dispose();
super.dispose();
}@override
Widget build(BuildContext context) {
return Container();
}
}
```All widgets that desire to use an `AnimationController` will have to reimplement
almost all of this logic from scratch, which is of course undesired.Dart mixins can partially solve this issue, but they suffer from other problems:
- A given mixin can only be used once per class.
- Mixins and the class share the same object.\
This means that if two mixins define a variable under the same name, the result
may vary between compilation fails to unknown behavior.---
This library proposes a third solution:
```dart
class Example extends HookWidget {
const Example({super.key, required this.duration});final Duration duration;
@override
Widget build(BuildContext context) {
final controller = useAnimationController(duration: duration);
return Container();
}
}
```This code is functionally equivalent to the previous example. It still disposes the
`AnimationController` and still updates its `duration` when `Example.duration` changes.
But you're probably thinking:> Where did all the logic go?
That logic has been moved into `useAnimationController`, a function included directly in
this library (see [Existing hooks](https://github.com/rrousselGit/flutter_hooks#existing-hooks)) - It is what we call a _Hook_.Hooks are a new kind of object with some specificities:
- They can only be used in the `build` method of a widget that mix-in `Hooks`.
- The same hook can be reused arbitrarily many times.
The following code defines two independent `AnimationController`, and they are
correctly preserved when the widget rebuild.```dart
Widget build(BuildContext context) {
final controller = useAnimationController();
final controller2 = useAnimationController();
return Container();
}
```- Hooks are entirely independent of each other and from the widget.\
This means that they can easily be extracted into a package and published on
[pub](https://pub.dev/) for others to use.## Principle
Similar to `State`, hooks are stored in the `Element` of a `Widget`. However, instead
of having one `State`, the `Element` stores a `List`. Then in order to use a `Hook`,
one must call `Hook.use`.The hook returned by `use` is based on the number of times it has been called.
The first call returns the first hook; the second call returns the second hook,
the third call returns the third hook and so on.If this idea is still unclear, a naive implementation of hooks could look as follows:
```dart
class HookElement extends Element {
List _hooks;
int _hookIndex;T use(Hook hook) => _hooks[_hookIndex++].build(this);
@override
performRebuild() {
_hookIndex = 0;
super.performRebuild();
}
}
```For more explanation of how hooks are implemented, here's a great article about
how it was done in React: https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e## Rules
Due to hooks being obtained from their index, some rules must be respected:
### DO always prefix your hooks with `use`:
```dart
Widget build(BuildContext context) {
// starts with `use`, good name
useMyHook();
// doesn't start with `use`, could confuse people into thinking that this isn't a hook
myHook();
// ....
}
```### DO call hooks unconditionally
```dart
Widget build(BuildContext context) {
useMyHook();
// ....
}
```### DON'T wrap `use` into a condition
```dart
Widget build(BuildContext context) {
if (condition) {
useMyHook();
}
// ....
}
```---
### About hot-reload
Since hooks are obtained from their index, one may think that hot-reloads while refactoring will break the application.
But worry not, a `HookWidget` overrides the default hot-reload behavior to work with hooks. Still, there are some situations in which the state of a Hook may be reset.
Consider the following list of hooks:
```dart
useA();
useB(0);
useC();
```Then consider that we edited the parameter of `HookB` after performing a hot-reload:
```dart
useA();
useB(42);
useC();
```Here everything works fine and all hooks maintain their state.
Now consider that we removed `HookB`. We now have:
```dart
useA();
useC();
```In this situation, `HookA` maintains its state but `HookC` gets hard reset.
This happens because, when a hot-reload is performed after refactoring, all hooks _after_ the first line impacted are disposed of.
So, since `HookC` was placed _after_ `HookB`, it will be disposed.## How to create a hook
There are two ways to create a hook:
- A function
Functions are by far the most common way to write hooks. Thanks to hooks being
composable by nature, a function will be able to combine other hooks to create
a more complex custom hook. By convention, these functions will be prefixed by `use`.The following code defines a custom hook that creates a variable and logs its value
to the console whenever the value changes:```dart
ValueNotifier useLoggedState([T initialData]) {
final result = useState(initialData);
useValueChanged(result.value, (_, __) {
print(result.value);
});
return result;
}
```- A class
When a hook becomes too complex, it is possible to convert it into a class that extends `Hook` - which can then be used using `Hook.use`.\
As a class, the hook will look very similar to a `State` class and have access to widget
life-cycle and methods such as `initHook`, `dispose` and `setState`.It is usually good practice to hide the class under a function as such:
```dart
Result useMyHook() {
return use(const _TimeAlive());
}
```The following code defines a hook that prints the total time a `State` has been alive on its dispose.
```dart
class _TimeAlive extends Hook {
const _TimeAlive();@override
_TimeAliveState createState() => _TimeAliveState();
}class _TimeAliveState extends HookState {
DateTime start;@override
void initHook() {
super.initHook();
start = DateTime.now();
}@override
void build(BuildContext context) {}@override
void dispose() {
print(DateTime.now().difference(start));
super.dispose();
}
}
```## Existing hooks
Flutter_Hooks already comes with a list of reusable hooks which are divided into different kinds:
### Primitives
A set of low-level hooks that interact with the different life-cycles of a widget
| Name | Description |
| -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| [useEffect](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useEffect.html) | Useful for side-effects and optionally canceling them. |
| [useState](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useState.html) | Creates a variable and subscribes to it. |
| [useMemoized](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useMemoized.html) | Caches the instance of a complex object. |
| [useRef](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useRef.html) | Creates an object that contains a single mutable property. |
| [useCallback](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useCallback.html) | Caches a function instance. |
| [useContext](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useContext.html) | Obtains the `BuildContext` of the building `HookWidget`. |
| [useValueChanged](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useValueChanged.html) | Watches a value and triggers a callback whenever its value changed. |### Object-binding
This category of hooks the manipulation of existing Flutter/Dart objects with hooks.
They will take care of creating/updating/disposing an object.#### dart:async related hooks:
| Name | Description |
| ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| [useStream](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useStream.html) | Subscribes to a `Stream` and returns its current state as an `AsyncSnapshot`. |
| [useStreamController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useStreamController.html) | Creates a `StreamController` which will automatically be disposed. |
| [useOnStreamChange](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOnStreamChange.html) | Subscribes to a `Stream`, registers handlers, and returns the `StreamSubscription`. |
| [useFuture](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useFuture.html) | Subscribes to a `Future` and returns its current state as an `AsyncSnapshot`. |#### Animation related hooks:
| Name | Description |
| ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| [useSingleTickerProvider](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useSingleTickerProvider.html) | Creates a single usage `TickerProvider`. |
| [useAnimationController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useAnimationController.html) | Creates an `AnimationController` which will be automatically disposed. |
| [useAnimation](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useAnimation.html) | Subscribes to an `Animation` and returns its value. |#### Listenable related hooks:
| Name | Description |
| -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| [useListenable](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useListenable.html) | Subscribes to a `Listenable` and marks the widget as needing build whenever the listener is called. |
| [useListenableSelector](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useListenableSelector.html) | Similar to `useListenable`, but allows filtering UI rebuilds |
| [useValueNotifier](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useValueNotifier.html) | Creates a `ValueNotifier` which will be automatically disposed. |
| [useValueListenable](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useValueListenable.html) | Subscribes to a `ValueListenable` and return its value. |
| [useOnListenableChange](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOnListenableChange.html) | Adds a given listener callback to a `Listenable` which will be automatically removed. |#### Misc hooks:
A series of hooks with no particular theme.
| Name | Description |
| ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| [useReducer](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useReducer.html) | An alternative to `useState` for more complex states. |
| [usePrevious](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/usePrevious.html) | Returns the previous argument called to [usePrevious]. |
| [useTextEditingController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTextEditingController-constant.html) | Creates a `TextEditingController`. |
| [useFocusNode](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useFocusNode.html) | Creates a `FocusNode`. |
| [useTabController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTabController.html) | Creates and disposes a `TabController`. |
| [useScrollController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useScrollController.html) | Creates and disposes a `ScrollController`. |
| [usePageController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/usePageController.html) | Creates and disposes a `PageController`. |
| [useFixedExtentScrollController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useFixedExtentScrollController.html) | Creates and disposes a `FixedExtentScrollController`. |
| [useAppLifecycleState](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useAppLifecycleState.html) | Returns the current `AppLifecycleState` and rebuilds the widget on change. |
| [useOnAppLifecycleStateChange](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOnAppLifecycleStateChange.html) | Listens to `AppLifecycleState` changes and triggers a callback on change. |
| [useTransformationController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTransformationController.html) | Creates and disposes a `TransformationController`. |
| [useIsMounted](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useIsMounted.html) | An equivalent to `State.mounted` for hooks. |
| [useAutomaticKeepAlive](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useAutomaticKeepAlive.html) | An equivalent to the `AutomaticKeepAlive` widget for hooks. |
| [useOnPlatformBrightnessChange](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOnPlatformBrightnessChange.html) | Listens to platform `Brightness` changes and triggers a callback on change. |
| [useSearchController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useSearchController.html) | Creates and disposes a `SearchController`. |
| [useWidgetStatesController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useWidgetStatesController.html) | Creates and disposes a `WidgetStatesController`. |
| [useExpansionTileController](https://api.flutter.dev/flutter/material/ExpansionTileController-class.html) | Creates a `ExpansionTileController`. |
| [useDebounced](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useDebounced.html) | Returns a debounced version of the provided value, triggering widget updates accordingly after a specified timeout duration |
| [useDraggableScrollableController](https://api.flutter.dev/flutter/widgets/DraggableScrollableController-class.html) | Creates a `DraggableScrollableController`. |## Contributions
Contributions are welcomed!
If you feel that a hook is missing, feel free to open a pull-request.
For a custom-hook to be merged, you will need to do the following:
- Describe the use-case.
Open an issue explaining why we need this hook, how to use it, ...
This is important as a hook will not get merged if the hook doesn't appeal to
a large number of people.If your hook is rejected, don't worry! A rejection doesn't mean that it won't
be merged later in the future if more people show interest in it.
In the mean-time, feel free to publish your hook as a package on https://pub.dev.- Write tests for your hook
A hook will not be merged unless fully tested to avoid inadvertently breaking it
in the future.- Add it to the README and write documentation for it.
## Sponsors