Giter Site home page Giter Site logo

rrousselgit / flutter_hooks Goto Github PK

View Code? Open in Web Editor NEW
3.0K 31.0 171.0 622 KB

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: MIT License

Dart 100.00%
dart flutter hook widget code-reuse hacktoberfest

flutter_hooks's Introduction

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 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:

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 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:

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 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) - 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.

    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 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<Hook>. 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:

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 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:

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-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:

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

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

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

Here everything works fine and all hooks maintain their state.

Now consider that we removed HookB. We now have:

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:

    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 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:

    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.

    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 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 Useful for side-effects and optionally canceling them.
useState Creates a variable and subscribes to it.
useMemoized Caches the instance of a complex object.
useRef Creates an object that contains a single mutable property.
useCallback Caches a function instance.
useContext Obtains the BuildContext of the building HookWidget.
useValueChanged 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 Subscribes to a Stream and returns its current state as an AsyncSnapshot.
useStreamController Creates a StreamController which will automatically be disposed.
useOnStreamChange Subscribes to a Stream, registers handlers, and returns the StreamSubscription.
useFuture Subscribes to a Future and returns its current state as an AsyncSnapshot.

Animation related hooks:

Name Description
useSingleTickerProvider Creates a single usage TickerProvider.
useAnimationController Creates an AnimationController which will be automatically disposed.
useAnimation Subscribes to an Animation and returns its value.

Listenable related hooks:

Name Description
useListenable Subscribes to a Listenable and marks the widget as needing build whenever the listener is called.
useListenableSelector Similar to useListenable, but allows filtering UI rebuilds
useValueNotifier Creates a ValueNotifier which will be automatically disposed.
useValueListenable Subscribes to a ValueListenable and return its value.

Misc hooks:

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 Creates a TextEditingController.
useFocusNode Creates 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 rebuilds the widget on change.
useOnAppLifecycleStateChange Listens to AppLifecycleState changes and triggers a callback on change.
useTransformationController Creates and disposes a TransformationController.
useIsMounted An equivalent to State.mounted for hooks.
useAutomaticKeepAlive An equivalent to the AutomaticKeepAlive widget for hooks.
useOnPlatformBrightnessChange Listens to platform Brightness changes and triggers a callback on change.
useSearchController Creates and disposes a SearchController.
useWidgetStatesController Creates and disposes a WidgetStatesController.
useExpansionTileController Creates a ExpansionTileController.
useDebounced Returns a debounced version of the provided value, triggering widget updates accordingly after a specified timeout duration

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

flutter_hooks's People

Contributors

0xflotus avatar albert221 avatar chunghha avatar creativecreatorormaybenot avatar davidmartos96 avatar devnico avatar droidbg avatar felangel avatar iamsahilsonawane avatar ipcjs avatar itisnajim avatar jezsung avatar kranfix avatar lucased78 avatar ohing504 avatar reedom avatar rjpower avatar ronnieeeeee avatar rrousselgit avatar sahandevs avatar sesam avatar simolus3 avatar smille avatar snapsl avatar subhendukundu avatar tanasehagi avatar tnorbury avatar trashfeed avatar vitusortner avatar yuzurihaaa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flutter_hooks's Issues

Add `didBuild` lifecycle

Some use-case such as mobxjs/mobx.dart#14 wants to trigger some custom logic right after the build method ended.

We can create a custom life-cycle on hooks to support this use-case:

class _SomeHookState extends HookState<T, Hook<T>> {
  @override 
  void didBuild() {
    // TODO: do something after `HookWidget.build` but because the children build.
  }
}
  • useAsyncEffect
  • useErrorBoundary

unsure how to solve 'mounted'

In this example, after i press the RaisedButton counter1 is incremented. This will cause HookBuilder to rebuild because of a new Key. The onPressed callback is still waiting until the future is completed and after that, counter2 should be incremented.
However, counter2 has been disposed because the Key changed.
In a StatefulWidget i could use this.mounted but i am not sure how to solve this problem with hooks.

class App extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final cb = useMemoized(() => () async {
          await Future.delayed(Duration(seconds: 3));
        });
    final counter1 = useState<int>(0);

    print('counter1 ${counter1.value}');

    return MaterialApp(
      home: Scaffold(
        body: Container(
          child: Center(
            child: Column(
              children: <Widget>[
                HookBuilder(
                    key: Key(counter1.value.toString()),
                    builder: (context) {
                      final counter2 = useState<int>(0);
                      print('counter2 ${counter2.value}');
                      return RaisedButton(onPressed: () async {
                        counter1.value += 1;
                        await cb();
                        counter2.value += 1;
                      });
                    }),
              ],
            ),
          ),
        ),
      ),
    );
  }
}


useContext hook

Thanks for including hooks to flutter. I thing that React hooks are nothing less than a revolution in (not only) React world...

Do you have a intention of including useContext (including createContext) too? I thing that it is very important hook primitive, which can in many use cases replace BLoC.

Dispose in reverse order

Currently, flutter_hooks disposes hooks from the top down, which is strange considering that Flutter itself disposes states from the bottom up. This causes exceptions when removing ChangeNotifier listeners that get cleaned up by useEffect's disposal callback.

useAnimationController

The code example useAnimationController is not available currently.
Can you provide and example that demonstrate Hook and HookState ?
Thx !

Provide an example using both Provider and Hooks

I'm a newbie - and perhaps this is not the right place for this sample, but I am not 100% clear on how hooks and Provider interact with each other, and what is the best practice.

For example, when do I want to use a ChangeNotifierProvider instead of a useListenable hook?

Can hooks pass providers down the tree to child widgets - or is that strictly what Providers is for?

If I pass down a ChangeNotifierProvider to a widget that uses hooks, how do I "wire" that in to the useListenable hook?

(Apologies if these questions don't make any sense!)

Provide better error message if hook is used outside HookWidget

First of all I'd like to thank you for this lib. It's awesome and I love it!!!

I'd like to request a better error message on this assertion because I've spent the last hour trying to debug my app to find that I was using a hook outside a HookWidget.

I'm having trouble finding out how to write a better error message because I'd like to know the name of the class that created the trouble so that the error message could be something like:

Error: The class _MyClassName doesn't extend HookWidget and is using a hook.

Thanks 👍

Allow adding hooks after an exception

Consider a build that first crashes then later rebuilds without a crash:

Widget build(BuildContext context) {
  final counter = useState(0);
   if (counter.value % 2 == 0) {
    counter.value++;
    throw counter.value;
  }
  final counter2 = useState(0);
  // ...
}

This doesn't make sense to prevent the addition of counter2 in this example and should be valid.

Exception throw during hot-reload

Sample:

@hwidget
Widget loading() {
  final animationController =
      useAnimationController(duration: const Duration(seconds: 2));
  useMemoized(animationController.forward, [animationController]);
  // useMemoized(animationController.stop, [animationController, 42]);
  final animation = useAnimation(animationController);
  final opacity = animation > .5 ? 1 - animation : animation;

  return Opacity(
    opacity: opacity,
    child: const Text('loading'),
  );
}

When switching between the commented and the uncommented useMemoized, an exception in throws

build method called repeatedly with useStream and dynamically created streams.

I wanted to replace the following snippet

Observable<int> someStream() => Observable.just(1);

return StreamBuilder<int>(
    builder: (context, snapshot) {
        stream: someStream(),
        return Text(snapshot.toString());
    }
);

with

Observable<int> someStream() => Observable.just(1);

return Text(useStream(someStream()).toString()); /// also tried it with preserveState = false

And the hooks way of doing this constantly rebuilds my widget as if I'd be calling setState on every build. Is this expected?

Thanks.

I'm on 0.3.0

why is the implementation of useEffect different from React hooks useEffect?

documentation for useEffect on react hooks page

The function passed to useEffect will run after the render is committed to the screen.

Flutter hooks synchronously executes the function which will lead in a different result as expected (coming from react hooks).

React also offers a hook called uselayouteffect that is more similar to flutter hooks useEffect.

#34 might be more similar to reacts useEffect.

Kind of confusing 😩 Maybe @rrousselGit can clarify some things here :)

Expose current HookElement

Now that HookContext isn't needed anymore, hooks don't receive a BuildContext as argument
This leads to making hooks not able to use InheritedWidget, which is undesired

To solve this issue, we need a way to expose the current building HookElement.

Cannot access state/hooks from local methods

When using regular StatefulWidget I can access the state from any method as the variables are declared as class members, but when using HookWidget there is no way to access them as hooks may only be created (and stored in the scope of) in the build method.

Is there something that I am missing? Or do I have to send state along as arguments to any method that need to mutate it ?

useAsyncEffect

We need a way to fire side-effects outside of the scope of build method.

A typical use-case is for pushing routes.

useLocalStorage()

Thanks for this library.

Made your example a bit more generic and complete. I'm new to Dart, so I'm sure there is a better way to do this than a big if tree.

import 'dart:async';

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

// A custom hook that will read and write values to local storage using the
// SharedPreferences package.
StreamController<T> useLocalStorage<T>(
  String key, T s, {
  T defaultValue,
}) {
  // Custom hooks can use additional hooks internally!
  final controller = useStreamController<T>(keys: [key]);

  // Pass a callback to the useEffect hook. This function should be called on
  // first build and every time the controller or key changes
  useEffect(
    () {
      // Listen to the StreamController, and when a value is added, store it
      // using SharedPrefs.
      final sub = controller.stream.listen((data) async {
        final prefs = await SharedPreferences.getInstance();
        if (s is String) {
          await prefs.setString(key, data as String);
        } else if (s is int) {
          await prefs.setInt(key, data as int);
        } else if (s is bool) {
          await prefs.setBool(key, data as bool);
        } else if (s is double) {
          await prefs.setDouble(key, data as double);
        } else if (s is List<String>) {
          await prefs.setStringList(key, data as List<String>);
        }
      });
      // Unsubscribe when the widget is disposed
      // or on controller/key change
      return sub.cancel;
    },
    // Pass the controller and key to the useEffect hook. This will ensure the
    // useEffect hook is only called the first build or when one of the the
    // values changes.
    [controller, key],
  );

  // Load the initial value from local storage and add it as the initial value
  // to the controller
  useEffect(
    () {
      SharedPreferences.getInstance().then((prefs) async {
        T valueFromStorage;
        if (s is String) {
          valueFromStorage = prefs.getString(key) as T;
        } else if (s is int) {
          valueFromStorage = prefs.getInt(key) as T;
        } else if (s is bool) {
          valueFromStorage = prefs.getBool(key) as T;
        } else if (s is double) {
          valueFromStorage = prefs.getDouble(key) as T;
        } else if (s is List<String>) {
          valueFromStorage = prefs.getStringList(key) as T;
        }

        controller.add(valueFromStorage ?? defaultValue);
      }).catchError(controller.addError);
      return;
    },
    // Pass the controller and key to the useEffect hook. This will ensure the
    // useEffect hook is only called the first build or when one of the the
    // values changes.
    [controller, key],
  );

  // Finally, return the StreamController. This allows users to add values from
  // the Widget layer and listen to the stream for changes.
  return controller;
}

Issue on Pub?

Couldn't find hooks on pub today after a bit of searching until I got to like the fourth page (searched for "hooks"). Looks like the library has been tagged with [UNIDENTIFIED] and has a couple of issues

Apologies if you were already aware of this

useStream bug in android release build

Hi,

I try latest v0.3.0-dev.3 on my project. It works well in emulators (both IOS and Android) but in Android release build, application crashes. When i remove useStream and useEffect it works again. Error is something like "Boolean expression not be null". Not remembering exactly.

Version 0.2.1 works well on release build.

By the way, great library. Thanks!

Flutter doctor output;

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel dev, v1.4.10, on Mac OS X 10.14.3 18D109, locale tr-TR)

[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[✓] iOS toolchain - develop for iOS devices (Xcode 10.2)
[✓] Android Studio (version 3.3)
[✓] IntelliJ IDEA Ultimate Edition (version 2018.3.5)
[✓] VS Code (version 1.33.0)
[✓] Connected device (2 available)

• No issues found!

Move away from StatefulWidget

Now that reassemble is public, we should theoretically be able to not depend on StatefulWidget anymore.

Let's remove this dependency

The following snippet causes an unclear error

 return HookBuilder(
        builder: (context) {
            final state = useState(true);

            if (state.value)
                return HookBuilder(
                    builder: (context) {
                        return IconButton(
                            onPressed: () => state.value = !state.value,
                            icon: Icon(Icons.add),
                        );
                    },
                );
            else
                return HookBuilder(
                    builder: (context) {
                        final x = useState(1);
                        return IconButton(
                            onPressed: () => state.value = !state.value,
                            icon: Text(x.value.toString()),
                        );
                    },
                );
        },
    );

A tap on the button will cause the following error:

flutter: The following assertion was thrown building HookBuilder(dirty, state: _HookWidgetState#ee3db):
flutter: 'package:flutter_hooks/src/framework.dart': Failed assertion: line 375 pos 14: '_debugDidReassemble
flutter: || !_didFinishBuildOnce': is not true.
flutter: 
flutter: Either the assertion indicates an error in the framework itself, or we should provide substantially
flutter: more information in this error message to help you determine and fix the underlying cause.
flutter: In either case, please report this assertion by filing a bug on GitHub:
flutter:   https://github.com/flutter/flutter/issues/new?template=BUG.md
flutter: 
flutter: Widget creation tracking is currently disabled. Enabling it enables improved error messages. It can
flutter: be enabled by passing `--track-widget-creation` to `flutter run` or `flutter test`.
flutter: 
flutter: When the exception was thrown, this was the stack:
flutter: #2      HookElement._use (package:flutter_hooks/src/framework.dart:375:14)
flutter: #3      Hook.use (package:flutter_hooks/src/framework.dart:136:40)
flutter: #4      useState (package:flutter_hooks/src/primitives.dart:206:15)
flutter: #5      wPortfolioPage.<anonymous closure>.<anonymous closure> (package:neptune_portfolio_flutter/src/route_portfolio_page.dart:31:35)
flutter: #6      HookBuilder.build (package:flutter_hooks/src/framework.dart:544:41)
flutter: #7      _HookWidgetState.build (package:flutter_hooks/src/framework.dart:515:19)
flutter: #8      StatefulElement.build (package:flutter/src/widgets/framework.dart:4040:27)
flutter: #9      HookElement.build (package:flutter_hooks/src/framework.dart:276:26)
flutter: #10     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3934:15)
flutter: #11     Element.rebuild (package:flutter/src/widgets/framework.dart:3731:5)
flutter: #12     StatefulElement.update (package:flutter/src/widgets/framework.dart:4113:5)
flutter: #13     Element.updateChild (package:flutter/src/widgets/framework.dart:2886:15)
flutter: #14     HookElement.updateChild (package:flutter_hooks/src/framework.dart:334:18)
flutter: #15     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3954:16)
flutter: #16     Element.rebuild (package:flutter/src/widgets/framework.dart:3731:5)
flutter: #17     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2341:33)
flutter: #18     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:761:20)
flutter: #19     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:280:5)
flutter: #20     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1031:15)
flutter: #21     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:973:9)
flutter: #22     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:889:5)
flutter: #26     _invoke (dart:ui/hooks.dart:249:10)
flutter: #27     _drawFrame (dart:ui/hooks.dart:207:3)
flutter: (elided 5 frames from class _AssertionError and package dart:async)
flutter: 
flutter: ════════════════════════════════════════════════════════════════════════════════════════════════════

The snippet doesn't make a lot of sense in this case but I need the use case where hookbuilders can be conditionally switched.
I know that one solution is to give the hookbuilders a key but shouldn't this actually not throw any error at all?

show dialog/snackbar etc

Hi, great lib, i love it :D!

I am wondering what is the best/recommended way to show a dialog/navigate in response to some state change.
int state = useStream(myStream); if (state == 5) { showDialog(context: context ....); }

Provide an example for useReducer for managing local state

I'm trying to create a simple demo for useReducer for managing local state.

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

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage2(title: 'Flutter Demo Home Page'),
    );
  }
}

class State {
  int counter = 0;
}

class IncrementAction {
  int count;
  IncrementAction({this.count});
}

class MyHomePage2 extends HookWidget {
  MyHomePage2({Key key, this.title}) : super(key: key);

  final String title;

  final State initialState = State();

  State reducer(State state, action) {
    if (action is IncrementAction) {
      state.counter = state.counter + action.count;
    }
    return state;
  }

  @override
  Widget build(BuildContext context) {
    final _store = useReducer(reducer, initialState: initialState);
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${_store.state.counter}',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _store.dispatch(IncrementAction(count: 1)),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

In the example above, the reducer gets called, the state gets updated, but the widget does not rerender.

Sorry if this seems like a question to be asked on stackoverflow, I could not find an example, I would like to donate my example to the docs after it gets fixed. :-)

Thanks

useMemoized with arguments

By principle, useMemoized must have all the variables used inside the callback as keys.

GOOD:

String id;
useMemoized(() {
  return fetch(id);
}, [id]);

BAD:

String id;
useMemoized(() {
  return fetch(id);
});

To make this easier safer, introducing new prototypes where keys are passed to the callback can be interesting:

useMemoized1<T1, R>(R callback(T1), T1 arg1);

used as followed:

useMemoized1(fetch, id);

setState() called after dispose()

flutter: setState() called after dispose(): _HookWidgetState#e2545(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose(). with ...

To avoid memory leaks, consider breaking the reference to this object during dispose()

Sometimes, the reference can't be broken without a lot of boilerplate for example when a Future is used.

Currently, there is no mounted property in HookState, is this intentional?

Tween hooks

Flutter offer multiple Tween classes.

A common use-case it to use them to tween between a previous and new value through didUpdateWidget.

Through hooks, this can be automated into a useTween:

final counter = useState(0);
final Tween tween = useTween(counter.value);

TextField will be cleared when the soft keyboard disappears

1.input text
2.close the softkeyboard,the text will be cleared

Every time the build call,the _oldPWControll print as a new instance

class Demo extends HookWidget {
  final _oldPWControll = TextEditingController();

  final _oldPWFN = FocusNode();
  @override
  Widget build(BuildContext context) {
    print(_oldPWControll);
    return Scaffold(
      resizeToAvoidBottomInset: true,
      body: TextField(
        controller: _oldPWControll,
        textInputAction: TextInputAction.next,
        focusNode: _oldPWFN,
        decoration: InputDecoration(
          hintText: "请输入",
          contentPadding: EdgeInsets.fromLTRB(35.0, 12.0, 12.0, 12.0),
        ),
      ),
    );
  }
}

When Demo as the home Widget of MaterialApp,it is work
but push to the Demo,problems will arise

more easy to use Context

I want to use context anywhere like State
I don't want to pass a context in the method,and I don't want to use HookBuilder to wrap the child widget。
how can i do


class Demo extends HookWidget{
  @override
  Widget build(BuildContext context) {
    
    return button();
  }

  Widget button(){
    return FlatButton(onPressed: (){
      // here need context
      Navigator.of(context).pushNamed("xx");
    },);
  }

}

Remove calls to `super.lifecycle` in existing hooks

In _AnimationControllerHookState the dispose override calls super.dispose before disposing the animation controller.

@override
 void dispose() {
   super.dispose();
   _animationController.dispose();
 }

I would expect the animation controller to be disposed first.

@override
 void dispose() {
   _animationController.dispose();
   super.dispose();
 }

Is this intentional? Thanks and great work 👍

Dealing with lists

Updating state of object of type List<T> either by direct assignment with new List or direct mutation; does't rebuild widget tree. I am currently after updating list, use notify listeners.

Thx

Equivalent of Widget.key for hooks

A very common use-case of hooks is to have a List that act as a Key like, for hooks.
Since it is so common, this library should provide some solution to unify this kind of logic.

I'm thinking of adding a List keys field to Hook as such:

abstract class Hook {
  const Hook({ this.keys });

  final List keys;
}

And trigger dispose followed by initHook on the HookState when there's a mismatch in the keys list.

[Example] Infinite scroll pagination with Firestore using hooks

Please provide an example of using hooks together with Firestore, in order to achieve a real time paginated ListView of a Firestore collection.

The most common approach to pagination could be showcased by:

  • Having an initial loading spinner
  • Hiding the spinner as soon as the first batch comes in
  • Requesting a new batch of documents as soon as the user scroll down and hits the bottom of the list(a loading spinner could be shown here until the requested batch arrives)
  • Having a message shown at the bottom of the list when we reach the end of Firestore's collection.

Preferably, the list should maintain it's real time functionality, meaning any additions, updates or deletes, should be reflected in the list.

ErrorDescription problem

Hi, I try to use your lib but I got always a compilation error: ErrorDescription like this:

 Error: The method 'ErrorDescription' isn't defined for the class 'HookElement'.
 - 'HookElement' is from 'package:flutter_hooks/src/framework.dart' ('file:///Users/isigest/Documents/Develop/Tools/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_hooks-0.4.0/lib/src/framework.dart').
Try correcting the name to the name of an existing method, or defining a method named 'ErrorDescription'.

Integrate Widget Inspector

Hook and HookState would gain a lot from inheriting Diagnosticable, so that the widget inspector allow debugging the hook state.

POC: Store hooks as a tree in development

From a discussion with Dan Abramov, it could be interesting to store hooks are a tree instead of list during development.

The benefits are that, as a tree, we have better integration with #15 and improved hot-reload

this is fucking briliant!

...just sayin'...

I use hooks with functional programming in react. Thank you for creating this project for flutter. BLoC who?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.