flutter_hooks 0.2.1

  • README.md
  • Example
  • Installing
  • Versions
  • 92

Build Status codecov pub package pub package

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 and as a complete replacement for StatefulWidget.

Motivation #

StatefulWidget suffer 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})
      : assert(duration != null),
        super(key: key);

  _ExampleState createState() => _ExampleState();

class _ExampleState extends State<Example> with SingleTickerProviderStateMixin {
  AnimationController _controller;

  void initState() {
    _controller = AnimationController(vsync: this, duration: widget.duration);

  void didUpdateWidget(Example oldWidget) {
    if (widget.duration != oldWidget.duration) {
      _controller.duration = widget.duration;

  void dispose() {

  Widget build(BuildContext context) {
    return Container();

All widgets that desire to use an AnimationController will have to reimplement almost of all 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 end result may vary between compilation fail to unknown behavior.

This library propose a third solution:

class Example extends HookWidget {
  final Duration duration;

  const Example({Key key, @required this.duration})
      : assert(duration != null),
        super(key: key);

  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 https://github.com/rrousselGit/flutter_hooks#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 HookWidget.
  • 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. Which 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);

  performRebuild() {
    _hookIndex = 0;

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, there are some rules that must be respected:

DO call use unconditionally

Widget build(BuildContext context) {
  // ....

DON'T wrap use into a condition

Widget build(BuildContext context) {
  if (condition) {
  // ....

DO always call all the hooks: #

Widget build(BuildContext context) {
  // ....

DON'T aborts build method before all hooks have been called:

Widget build(BuildContext context) {
  if (condition) {
    return Container();
  // ....

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:


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


Here everything works fine; all hooks keep their states.

Now consider that we removed HookB. We now have:


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. Since HookC was placed after HookB, is got disposed.

How to use #

There are two ways to create a hook:

  • A function

Functions is 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, (_, __) {
  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 Hook.use(_MyHook());

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

class _TimeAlive<T> extends Hook<void> {
  const _TimeAlive();

  _TimeAliveState<T> createState() => _TimeAliveState<T>();

class _TimeAliveState<T> extends HookState<void, _TimeAlive<T>> {
  DateTime start;

  void initHook() {
    start = DateTime.now();

  void build(BuildContext context) {
    // this hook doesn't create anything nor uses other hooks

  void dispose() {

Existing hooks #

Flutter_hooks comes with a list of reusable hooks already provided. They are static methods free to use that includes:

  • useEffect

Useful to trigger side effects in a widget and dispose objects. It takes a callback and calls it immediately. That callback may optionally return a function, which will be called when the widget is disposed.

By default, the callback is called on every build, but it is possible to override that behavior by passing a list of objects as the second parameter. The callback will then be called only when something inside the list has changed.

The following call to useEffect subscribes to a Stream and cancel the subscription when the widget is disposed:

Stream stream;
useEffect(() {
    final subscription = stream.listen(print);
    // This will cancel the subscription when the widget is disposed
    // or if the callback is called again.
    return subscription.cancel;
  // when the stream change, useEffect will call the callback again.
  • useState

Defines + watch a variable and whenever the value change, calls setState.

The following code uses useState to make a counter application:

class Counter extends HookWidget {
  Widget build(BuildContext context) {
    final counter = useState(0);

    return GestureDetector(
      // automatically triggers a rebuild of Counter widget
      onTap: () => counter.value++,
      child: Text(counter.value.toString()),
  • useReducer

An alternative to useState for more complex states.

useReducer manages an read only state that can be updated by dispatching actions which are interpreted by a Reducer.

The following makes a counter app with both a "+1" and "-1" button:

class Counter extends HookWidget {
  Widget build(BuildContext context) {
    final counter = useReducer(_counterReducer, initialState: 0);

    return Column(
      children: <Widget>[
          icon: const Icon(Icons.add),
          onPressed: () => counter.dispatch('increment'),
          icon: const Icon(Icons.remove),
          onPressed: () => counter.dispatch('decrement'),

  int _counterReducer(int state, String action) {
    switch (action) {
      case 'increment':
        return state + 1;
      case 'decrement':
        return state - 1;
        return state;
  • useContext

Returns the BuildContext of the currently building HookWidget. This is useful when writing custom hooks that want to manipulate the BuildContext.

MyInheritedWidget useMyInheritedWidget() {
  BuildContext context = useContext();
  return MyInheritedWidget.of(context);
  • useMemoized

Takes a callback, calls it synchronously and returns its result. The result is then stored to that subsequent calls will return the same result without calling the callback.

By default, the callback is called only on the first build. But it is optionally possible to specify a list of objects as the second parameter. The callback will then be called again whenever something inside the list has changed.

The following sample make an http call and return the created Future. And if userId changes, a new call will be made:

String userId;
final Future<http.Response> response = useMemoized(() {
  return http.get('someUrl/$userId');
}, [userId]);
  • useValueChanged

Takes a value and a callback, and call the callback whenever the value changed. The callback can optionally return an object, which will be stored and returned as the result of useValueChanged.

The following example implicitly starts a tween animation whenever color changes:

AnimationController controller;
Color color;

final colorTween = useValueChanged(
    (Color oldColor, Animation<Color> oldAnimation) {
      return ColorTween(
        begin: oldAnimation?.value ?? oldColor,
        end: color,
      ).animate(controller..forward(from: 0));
  ) ??
  • useAnimationController, useStreamController, useSingleTickerProvider, useValueNotifier

A set of hooks that handles the whole life-cycle of an object. These hooks will take care of both creating, disposing and updating the object.

They are the equivalent of both initState, dispose and didUpdateWidget for that specific object.

Duration duration;
AnimationController controller = useAnimationController(
  // duration is automatically updates when the widget is rebuilt with a different `duration`
  duration: duration,
  • useStream, useFuture, useAnimation, useValueListenable, useListenable

A set of hooks that subscribes to an object and calls setState accordingly.

Stream<int> stream;
// automatically rebuild the widget when a new value is pushed to the stream
AsyncSnapshot<int> snapshot = useStream(stream);

0.2.1: #

  • NEW: useValueNotifier, which creates a ValueNotifier similarly to useState. But without listening it. This can be useful to have a more granular rebuild when combined to useValueListenable.
  • NEW: useContext, which exposes the BuildContext of the currently building HookWidget.

0.2.0: #

  • Made all existing hooks as static functions, and removed HookContext. The migration is as followed:
Widget build(HookContext context) {
    final state = context.useState(0);


Widget build(BuildContext context) {
    final state = useState(0);
  • Introduced keys for hooks and applied them to hooks where it makes sense.
  • Added useReducer for complex state. It is similar to useState but is being managed by a Reducer and can only be changed by dispatching an action.
  • fixes a bug where hot-reload without using hooks throwed an exception

0.1.0: #

  • useMemoized callback doesn't take the previous value anymore (to match React API) Use useValueChanged instead.
  • Introduced useEffect and useStreamController
  • fixed a bug where hot-reload while reordering/adding hooks did not work properly
  • improved readme

0.0.1: #

Added a few common hooks:

  • useStream
  • useFuture
  • useAnimationController
  • useSingleTickerProvider
  • useListenable
  • useValueListenable
  • useAnimation

0.0.0: #

  • initial release


// ignore_for_file: omit_local_variable_types
import 'dart:async';

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

void main() => runApp(_MyApp());

class _MyApp extends StatelessWidget {
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      home: _Counter(),

class _Counter extends HookWidget {
  const _Counter({Key key}) : super(key: key);

  Widget build(BuildContext context) {
    StreamController<int> countController =
        _useLocalStorageInt(context, 'counter');
    return Scaffold(
      appBar: AppBar(
        title: const Text('Counter app'),
      body: Center(
        child: HookBuilder(
          builder: (context) {
            AsyncSnapshot<int> count = useStream(countController.stream);

            return !count.hasData
                // Currently loading value from local storage, or there's an error
                ? const CircularProgressIndicator()
                : GestureDetector(
                    onTap: () => countController.add(count.data + 1),
                    child: Text('You tapped me ${count.data} times.'),

StreamController<int> _useLocalStorageInt(
  BuildContext context,
  String key, {
  int defaultValue = 0,
}) {
  final controller = useStreamController<int>(keys: <dynamic>[key]);

  // We define a callback that will be called on first build
  // and whenever the controller/key change
  useEffect(() {
    // We listen to the data and push new values to local storage
    final sub = controller.stream.listen((data) async {
      final prefs = await SharedPreferences.getInstance();
      await prefs.setInt(key, data);
    // Unsubscribe when the widget is disposed
    // or on controller/key change
    return sub.cancel;
  }, <dynamic>[controller, key]);
  // We load the initial value
  useEffect(() {
    SharedPreferences.getInstance().then((prefs) async {
      int valueFromStorage = prefs.getInt(key);
      controller.add(valueFromStorage ?? defaultValue);
    // ensure the callback is called only on first build
  }, <dynamic>[controller, key]);

  return controller;

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:

  flutter_hooks: ^0.2.1

2. Install it

You can install packages from the command line:

with Flutter:

$ flutter packages get

Alternatively, your editor might support flutter packages get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:

import 'package:flutter_hooks/flutter_hooks.dart';
Version Uploaded Documentation Archive
0.2.1 Jan 6, 2019 Go to the documentation of flutter_hooks 0.2.1 Download flutter_hooks 0.2.1 archive
0.2.0 Dec 28, 2018 Go to the documentation of flutter_hooks 0.2.0 Download flutter_hooks 0.2.0 archive
0.1.0 Dec 24, 2018 Go to the documentation of flutter_hooks 0.1.0 Download flutter_hooks 0.1.0 archive
0.0.1+2 Dec 24, 2018 Go to the documentation of flutter_hooks 0.0.1+2 Download flutter_hooks 0.0.1+2 archive
0.0.1+1 Dec 24, 2018 Go to the documentation of flutter_hooks 0.0.1+1 Download flutter_hooks 0.0.1+1 archive
0.0.1 Dec 21, 2018 Go to the documentation of flutter_hooks 0.0.1 Download flutter_hooks 0.0.1 archive
0.0.0+2 Dec 19, 2018 Go to the documentation of flutter_hooks 0.0.0+2 Download flutter_hooks 0.0.0+2 archive
0.0.0+1 Dec 17, 2018 Go to the documentation of flutter_hooks 0.0.0+1 Download flutter_hooks 0.0.0+1 archive
0.3.1-dev Apr 10, 2019 Go to the documentation of flutter_hooks 0.3.1-dev Download flutter_hooks 0.3.1-dev archive
0.3.0-dev.4 Feb 19, 2019 Go to the documentation of flutter_hooks 0.3.0-dev.4 Download flutter_hooks 0.3.0-dev.4 archive

All 14 versions...

Describes how popular the package is relative to other packages. [more]
Code health derived from static analysis. [more]
Reflects how tidy and up-to-date the package is. [more]
Weighted score of the above. [more]
Learn more about scoring.

We analyzed this package on Apr 16, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.2.0
  • pana: 0.12.14
  • Flutter: 1.4.7


Detected platforms: Flutter

References Flutter, and has no conflicting libraries.


Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.0.0-dev.68.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11
meta 1.1.6 1.1.7
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
mockito >=4.0.0 <5.0.0
pedantic ^1.4.0