All Projects → rrousselGit → Flutter_hooks

rrousselGit / Flutter_hooks

Licence: mit
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.

Programming Languages

dart
5743 projects

Projects that are alternatives of or similar to Flutter hooks

Logrus Logstash Hook
ℹ️ Logstash hook for logrus
Stars: ✭ 150 (-92.4%)
Mutual labels:  hacktoberfest, hook
Useeffectreducer
useReducer + useEffect = useEffectReducer
Stars: ✭ 642 (-67.46%)
Mutual labels:  hacktoberfest, hook
flhooks
React like Hooks implementation for Flutter.
Stars: ✭ 38 (-98.07%)
Mutual labels:  widget, code-reuse
Yii2 Quill
Yii 2 implementation of Quill, modern WYSIWYG editor
Stars: ✭ 52 (-97.36%)
Mutual labels:  hacktoberfest, widget
Avatarview
A circular Image View with a lot of perks. Including progress animation and highlight state with borders and gradient color.
Stars: ✭ 429 (-78.26%)
Mutual labels:  hacktoberfest, widget
Dev Widget
Unofficial Widget/profile card for https://dev.to/
Stars: ✭ 91 (-95.39%)
Mutual labels:  hacktoberfest, widget
Fabric Samples
wiki.hyperledger.org/display/fabric
Stars: ✭ 1,980 (+0.35%)
Mutual labels:  hacktoberfest
Remoting
Jenkins Remoting module
Stars: ✭ 132 (-93.31%)
Mutual labels:  hacktoberfest
Rawcms
RawCMS is the headless CMS written in asp.net core build for developers that embraces API first technology. Please give us a feedback!
Stars: ✭ 132 (-93.31%)
Mutual labels:  hacktoberfest
Cls Proxify
Logging on steroids with CLS and Proxy. Integrated with express, koa, fastify.
Stars: ✭ 132 (-93.31%)
Mutual labels:  hacktoberfest
Inertia
✈️ Effortless, self-hosted continuous deployment for small teams and projects
Stars: ✭ 133 (-93.26%)
Mutual labels:  hacktoberfest
Ephemeral
A private-by-default, always-incognito browser for elementary OS
Stars: ✭ 133 (-93.26%)
Mutual labels:  hacktoberfest
Inwidget
inWidget - free Instagram widget for your website. Allows you to show photos from an Instagram account, by hashtags and more.
Stars: ✭ 132 (-93.31%)
Mutual labels:  widget
Boop
A scriptable scratchpad for developers. In slow yet steady progress.
Stars: ✭ 2,425 (+22.91%)
Mutual labels:  hacktoberfest
Silverstripe Userforms
UserForms module provides a visual form builder for the SilverStripe CMS. No coding required to build forms such as contact pages.
Stars: ✭ 132 (-93.31%)
Mutual labels:  hacktoberfest
Mentorship Backend
Mentorship System is an application that matches women in tech to mentor each other, on career development, through 1:1 relations during a certain period of time. This is the backend of this system.
Stars: ✭ 132 (-93.31%)
Mutual labels:  hacktoberfest
Py Readability Metrics
📗 Score text readability using a number of formulas: Flesch-Kincaid Grade Level, Gunning Fog, ARI, Dale Chall, SMOG, and more
Stars: ✭ 132 (-93.31%)
Mutual labels:  hacktoberfest
Pesy
Project configuration for esy
Stars: ✭ 132 (-93.31%)
Mutual labels:  hacktoberfest
Wopihost
ASP.NET Core MVC implementation of the WOPI protocol. Enables integration with WOPI clients such as Office Online Server.
Stars: ✭ 132 (-93.31%)
Mutual labels:  hacktoberfest
Cypress Schematic
Add cypress to an Angular CLI project
Stars: ✭ 132 (-93.31%)
Mutual labels:  hacktoberfest

English | Português

Build codecov pub package pub package Discord

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 manages a Widget life-cycles. 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:

class Example extends StatefulWidget {
  final Duration duration;

  const Example({Key key, required this.duration})
      : super(key: key);

  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> with SingleTickerProviderStateMixin {
  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 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 shares 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:

class Example extends HookWidget {
  const Example({Key key, required this.duration})
      : super(key: key);

  final Duration duration;

  @override
  Widget build(BuildContext context) {
    final controller = useAnimationController(duration: duration);
    return Container();
  }
}

This code is strictly 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 moved into useAnimationController, a function included directly in this library (see Existing hooks). It is what we call a Hook.

Hooks are a new kind of objects with some specificities:

  • They can only be used in the build method of a widget that mix-in Hooks.

  • The same hook is reusable an infinite number of times The following code defines two independent AnimationController, and they are correctly preserved when the widget rebuild.

    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 they can easily be extracted into a package and published on pub for others to use.

Principle

Similarly to State, hooks are stored on the Element of a Widget. But instead of having one State, the Element stores a List<Hook>. Then 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 returns the third hook, ...

If this is still unclear, a naive implementation of hooks is the following:

class HookElement extends Element {
  List<HookState> _hooks;
  int _hookIndex;

  T use<T>(Hook<T> hook) => _hooks[_hookIndex++].build(this);

  @override
  performRebuild() {
    _hookIndex = 0;
    super.performRebuild();
  }
}

For more explanation of how they are implemented, here's a great article about how they did it 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:

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

Widget build(BuildContext context) {
  useMyHook();
  // ....
}

DON'T wrap use into a condition

Widget build(BuildContext context) {
  if (condition) {
    useMyHook();
  }
  // ....
}

About hot-reload

Since hooks are obtained from their index, one may think that hot-reload while refactoring will break the application.

But worry not, HookWidget overrides the default hot-reload behavior to work with hooks. Still, there are some situations in which the state of a Hook may get reset.

Consider the following list of hooks:

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

Then consider that after a hot-reload, we edited the parameter of HookB:

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

Here everything works fine; all hooks keep their states.

Now consider that we removed HookB. We now have:

useA();
useC();

In this situation, HookA keeps its state but HookC gets a hard reset. This happens because when a refactoring is done, all hooks after the first line impacted are disposed of. Since HookC was placed after HookB, it got disposed of.

How to use

There are two ways to create a hook:

  • A function

    Functions are by far the most common way to write a hook. Thanks to hooks being composable by nature, a function will be able to combine other hooks to create a custom hook. By convention, these functions will be prefixed by use.

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

    ValueNotifier<T> useLoggedState<T>(BuildContext context, [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 extends Hook, which can then be used using Hook.use.
    As a class, the hook will look very similar to a State and have access to life-cycles and methods such as initHook, dispose and setState It is usually a good practice to hide the class under a function as such:

    Result useMyHook(BuildContext context) {
      return use(const _TimeAlive());
    }

    The following defines a hook that prints the time a State has been alive.

    class _TimeAlive extends Hook<void> {
      const _TimeAlive();
    
      @override
      _TimeAliveState createState() => _TimeAliveState();
    }
    
    class _TimeAliveState extends HookState<void, _TimeAlive> {
      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 comes with a list of reusable hooks already provided.

They are divided into different kinds:

Primitives

A set of low-level hooks that interacts with the different life-cycles of a widget

name description
useEffect Useful for side-effects and optionally canceling them.
useState Create variable and subscribes to it.
useMemoized Cache the instance of a complex object.
useRef Creates an object that contains a single mutable property.
useCallback Cache a function instance.
useContext Obtain the BuildContext of the building HookWidget.
useValueChanged Watches a value and calls a callback whenever the value changed.

Object binding

This category of hooks allows manipulating existing Flutter/Dart objects with hooks. They will take care of creating/updating/disposing an object.

dart:async related:

name description
useStream Subscribes to a Stream and return its current state in an AsyncSnapshot.
useStreamController Creates a StreamController automatically disposed.
useFuture Subscribes to a Future and return its current state in an AsyncSnapshot.

Animation related:

name description
useSingleTickerProvider Creates a single usage TickerProvider.
useAnimationController Creates an AnimationController automatically disposed.
useAnimation Subscribes to an Animation and return its value.

Listenable related:

name description
useListenable Subscribes to a Listenable and mark the widget as needing build whenever the listener is called.
useValueNotifier Creates a ValueNotifier automatically disposed.
useValueListenable Subscribes to a ValueListenable and return its value.

Misc

A series of hooks with no particular theme.

name description
useReducer An alternative to useState for more complex states.
usePrevious Returns the previous argument called to [usePrevious].
useTextEditingController Create a TextEditingController
useFocusNode Create a FocusNode
useTabController Creates and disposes a TabController.
useScrollController Creates and disposes a ScrollController.
usePageController Creates and disposes a PageController.
useAppLifecycleState Returns the current AppLifecycleState and rebuild the widget on change.
useOnAppLifecycleStateChange Listens to AppLifecycleState changes and call a callback on change.
useTransformationController Creates and disposes a TransformationController.
useIsMounted An equivalent to State.mounted for hooks

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 shows an 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 unles fully tested, to avoid breaking it inadvertendly in the future.

  • Add it to the Readme & write documentation for it.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].