Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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.

License

NotificationsYou must be signed in to change notification settings

rrousselGit/flutter_hooks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 

Repository files navigation

English |Português |한국어 |简体中文 |日本語

Buildcodecovpub packagepub packageDiscord

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 aWidget. They existfor one reason: increase the code-sharingbetween widgets by removing duplicates.

Motivation

StatefulWidget suffers from a big problem: it is very difficult to reuse thelogic of sayinitState ordispose. An obvious example isAnimationController:

classExampleextendsStatefulWidget {constExample({super.key,requiredthis.duration});finalDuration duration;@override_ExampleStatecreateState()=>_ExampleState();}class_ExampleStateextendsState<Example>withSingleTickerProviderStateMixin {latefinalAnimationController _controller;@overridevoidinitState() {super.initState();    _controller=AnimationController(vsync:this, duration: widget.duration);  }@overridevoiddidUpdateWidget(Example oldWidget) {super.didUpdateWidget(oldWidget);if (widget.duration!= oldWidget.duration) {      _controller.duration= widget.duration;    }  }@overridevoiddispose() {    _controller.dispose();super.dispose();  }@overrideWidgetbuild(BuildContext context) {returnContainer();  }}

All widgets that desire to use anAnimationController will have to reimplementalmost 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 resultmay vary between compilation fails to unknown behavior.

This library proposes a third solution:

classExampleextendsHookWidget {constExample({super.key,requiredthis.duration});finalDuration duration;@overrideWidgetbuild(BuildContext context) {final controller=useAnimationController(duration: duration);returnContainer();  }}

This code is functionally equivalent to the previous example. It still disposes theAnimationController and still updates itsduration whenExample.duration changes.But you're probably thinking:

Where did all the logic go?

That logic has been moved intouseAnimationController, a function included directly inthis library (seeExisting hooks) - It is what we call aHook.

Hooks are a new kind of object with some specificities:

  • They can only be used in thebuild method of a widget that mix-inHooks.

  • The same hook can be reused arbitrarily many times.The following code defines two independentAnimationController, and they arecorrectly preserved when the widget rebuild.

    Widgetbuild(BuildContext context) {final controller=useAnimationController();final controller2=useAnimationController();returnContainer();}
  • Hooks are entirely independent of each other and from the widget.
    This means that they can easily be extracted into a package and published onpub for others to use.

Principle

Similar toState, hooks are stored in theElement of aWidget. However, insteadof having oneState, theElement stores aList<Hook>. Then in order to use aHook,one must callHook.use.

The hook returned byuse 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:

classHookElementextendsElement {List<HookState> _hooks;int _hookIndex;Tuse<T>(Hook<T> hook)=> _hooks[_hookIndex++].build(this);@overrideperformRebuild() {    _hookIndex=0;super.performRebuild();  }}

For more explanation of how hooks are implemented, here's a great article abouthow 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 withuse:

Widgetbuild(BuildContext context) {// starts with `use`, good nameuseMyHook();// doesn't start with `use`, could confuse people into thinking that this isn't a hookmyHook();// ....}

DO call hooks unconditionally

Widgetbuild(BuildContext context) {useMyHook();// ....}

DON'T wrapuse into a condition

Widgetbuild(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, aHookWidget 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:

useA();useB(0);useC();

Then consider that we edited the parameter ofHookB after performing a hot-reload:

useA();useB(42);useC();

Here everything works fine and all hooks maintain their state.

Now consider that we removedHookB. We now have:

useA();useC();

In this situation,HookA maintains its state butHookC gets hard reset.This happens because, when a hot-reload is performed after refactoring, all hooksafter the first line impacted are disposed of.So, sinceHookC was placedafterHookB, 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 beingcomposable by nature, a function will be able to combine other hooks to createa more complex custom hook. By convention, these functions will be prefixed byuse.

    The following code defines a custom hook that creates a variable and logs its valueto the console whenever the value changes:

    ValueNotifier<T>useLoggedState<T>([T initialData]) {final result=useState<T>(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 extendsHook - which can then be used usingHook.use.
    As a class, the hook will look very similar to aState class and have access to widgetlife-cycle and methods such asinitHook,dispose andsetState.

    It is usually good practice to hide the class under a function as such:

    ResultuseMyHook() {returnuse(const_TimeAlive());}

    The following code defines a hook that prints the total time aState has been alive on its dispose.

    class_TimeAliveextendsHook<void> {const_TimeAlive();@override_TimeAliveStatecreateState()=>_TimeAliveState();}class_TimeAliveStateextendsHookState<void,_TimeAlive> {DateTime start;@overridevoidinitHook() {super.initHook();    start=DateTime.now();  }@overridevoidbuild(BuildContext context) {}@overridevoiddispose() {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

NameDescription
useEffectUseful for side-effects and optionally canceling them.
useStateCreates a variable and subscribes to it.
useMemoizedCaches the instance of a complex object.
useRefCreates an object that contains a single mutable property.
useCallbackCaches a function instance.
useContextObtains theBuildContext of the buildingHookWidget.
useValueChangedWatches 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:

NameDescription
useStreamSubscribes to aStream and returns its current state as anAsyncSnapshot.
useStreamControllerCreates aStreamController which will automatically be disposed.
useOnStreamChangeSubscribes to aStream, registers handlers, and returns theStreamSubscription.
useFutureSubscribes to aFuture and returns its current state as anAsyncSnapshot.

Animation related hooks:

NameDescription
useSingleTickerProviderCreates a single usageTickerProvider.
useAnimationControllerCreates anAnimationController which will be automatically disposed.
useAnimationSubscribes to anAnimation and returns its value.

Listenable related hooks:

NameDescription
useListenableSubscribes to aListenable and marks the widget as needing build whenever the listener is called.
useListenableSelectorSimilar touseListenable, but allows filtering UI rebuilds
useValueNotifierCreates aValueNotifier which will be automatically disposed.
useValueListenableSubscribes to aValueListenable and return its value.
useOnListenableChangeAdds a given listener callback to aListenable which will be automatically removed.

Misc hooks:

A series of hooks with no particular theme.

NameDescription
useReducerAn alternative touseState for more complex states.
usePreviousReturns the previous argument called to [usePrevious].
useTextEditingControllerCreates aTextEditingController.
useFocusNodeCreates aFocusNode.
useTabControllerCreates and disposes aTabController.
useScrollControllerCreates and disposes aScrollController.
usePageControllerCreates and disposes aPageController.
useFixedExtentScrollControllerCreates and disposes aFixedExtentScrollController.
useAppLifecycleStateReturns the currentAppLifecycleState and rebuilds the widget on change.
useOnAppLifecycleStateChangeListens toAppLifecycleState changes and triggers a callback on change.
useTransformationControllerCreates and disposes aTransformationController.
useIsMountedAn equivalent toState.mounted for hooks.
useAutomaticKeepAliveAn equivalent to theAutomaticKeepAlive widget for hooks.
useOnPlatformBrightnessChangeListens to platformBrightness changes and triggers a callback on change.
useSearchControllerCreates and disposes aSearchController.
useWidgetStatesControllerCreates and disposes aWidgetStatesController.
useExpansibleControllerCreates aExpansibleController.
useDebouncedReturns a debounced version of the provided value, triggering widget updates accordingly after a specified timeout duration
useDraggableScrollableControllerCreates aDraggableScrollableController.
useCarouselControllerCreates and disposes aCarouselController.
useTreeSliverControllerCreates aTreeSliverController.
useOverlayPortalControllerCreates and manages anOverlayPortalController for controlling the visibility of overlay content. The controller will be automatically disposed when no longer needed.

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 toa large number of people.

    If your hook is rejected, don't worry! A rejection doesn't mean that it won'tbe 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 onhttps://pub.dev.

  • Write tests for your hook

    A hook will not be merged unless fully tested to avoid inadvertently breaking itin the future.

  • Add it to the README and write documentation for it.

Sponsors

About

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.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Contributors70

Languages


[8]ページ先頭

©2009-2025 Movatter.jp