Created
December 1, 2019 22:55
-
-
Save leecommamichael/28f290c3bc6f348de63b3fb5c300538d to your computer and use it in GitHub Desktop.
Hiding a package in DartPad
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/scheduler.dart'; | |
import 'package:flutter/widgets.dart'; | |
import 'dart:async'; | |
T useAnimation<T>(Animation<T> animation) { | |
useListenable(animation); | |
return animation.value; | |
} | |
AnimationController useAnimationController({ | |
Duration duration, | |
String debugLabel, | |
double initialValue = 0, | |
double lowerBound = 0, | |
double upperBound = 1, | |
TickerProvider vsync, | |
AnimationBehavior animationBehavior = AnimationBehavior.normal, | |
List<Object> keys, | |
}) { | |
return Hook.use(_AnimationControllerHook( | |
duration: duration, | |
debugLabel: debugLabel, | |
initialValue: initialValue, | |
lowerBound: lowerBound, | |
upperBound: upperBound, | |
vsync: vsync, | |
animationBehavior: animationBehavior, | |
keys: keys, | |
)); | |
} | |
class _AnimationControllerHook extends Hook<AnimationController> { | |
final Duration duration; | |
final String debugLabel; | |
final double initialValue; | |
final double lowerBound; | |
final double upperBound; | |
final TickerProvider vsync; | |
final AnimationBehavior animationBehavior; | |
const _AnimationControllerHook({ | |
this.duration, | |
this.debugLabel, | |
this.initialValue, | |
this.lowerBound, | |
this.upperBound, | |
this.vsync, | |
this.animationBehavior, | |
List<Object> keys, | |
}) : super(keys: keys); | |
@override | |
_AnimationControllerHookState createState() => | |
_AnimationControllerHookState(); | |
} | |
class _AnimationControllerHookState | |
extends HookState<AnimationController, _AnimationControllerHook> { | |
AnimationController _animationController; | |
@override | |
void didUpdateHook(_AnimationControllerHook oldHook) { | |
super.didUpdateHook(oldHook); | |
if (hook.vsync != oldHook.vsync) { | |
assert(hook.vsync != null && oldHook.vsync != null, ''' | |
Switching between controller and uncontrolled vsync is not allowed. | |
'''); | |
_animationController.resync(hook.vsync); | |
} | |
if (hook.duration != oldHook.duration) { | |
_animationController.duration = hook.duration; | |
} | |
} | |
@override | |
AnimationController build(BuildContext context) { | |
final vsync = hook.vsync ?? useSingleTickerProvider(keys: hook.keys); | |
_animationController ??= AnimationController( | |
vsync: vsync, | |
duration: hook.duration, | |
debugLabel: hook.debugLabel, | |
lowerBound: hook.lowerBound, | |
upperBound: hook.upperBound, | |
animationBehavior: hook.animationBehavior, | |
value: hook.initialValue, | |
); | |
return _animationController; | |
} | |
@override | |
void dispose() { | |
_animationController.dispose(); | |
} | |
} | |
TickerProvider useSingleTickerProvider({List<Object> keys}) { | |
return Hook.use( | |
keys != null | |
? _SingleTickerProviderHook(keys) | |
: const _SingleTickerProviderHook(), | |
); | |
} | |
class _SingleTickerProviderHook extends Hook<TickerProvider> { | |
const _SingleTickerProviderHook([List<Object> keys]) : super(keys: keys); | |
@override | |
_TickerProviderHookState createState() => _TickerProviderHookState(); | |
} | |
class _TickerProviderHookState | |
extends HookState<TickerProvider, _SingleTickerProviderHook> | |
implements TickerProvider { | |
Ticker _ticker; | |
@override | |
Ticker createTicker(TickerCallback onTick) { | |
assert(() { | |
if (_ticker == null) return true; | |
throw FlutterError( | |
'${context.widget.runtimeType} attempted to use a useSingleTickerProvider multiple times.\n' | |
'A SingleTickerProviderStateMixin can only be used as a TickerProvider once. If a ' | |
'TickerProvider is used for multiple AnimationController objects, or if it is passed to other ' | |
'objects and those objects might use it more than one time in total, then instead of ' | |
'using useSingleTickerProvider, use a regular useTickerProvider.'); | |
}()); | |
_ticker = Ticker(onTick, debugLabel: 'created by $context'); | |
return _ticker; | |
} | |
@override | |
void dispose() { | |
assert(() { | |
if (_ticker == null || !_ticker.isActive) return true; | |
throw FlutterError( | |
'useSingleTickerProvider created a Ticker, but at the time ' | |
'dispose() was called on the Hook, that Ticker was still active. Tickers used ' | |
' by AnimationControllers should be disposed by calling dispose() on ' | |
' the AnimationController itself. Otherwise, the ticker will leak.\n'); | |
}()); | |
} | |
@override | |
TickerProvider build(BuildContext context) { | |
if (_ticker != null) _ticker.muted = !TickerMode.of(context); | |
return this; | |
} | |
} | |
AsyncSnapshot<T> useFuture<T>(Future<T> future, | |
{T initialData, bool preserveState = true}) { | |
return Hook.use(_FutureHook(future, | |
initialData: initialData, preserveState: preserveState)); | |
} | |
class _FutureHook<T> extends Hook<AsyncSnapshot<T>> { | |
final Future<T> future; | |
final bool preserveState; | |
final T initialData; | |
const _FutureHook(this.future, {this.initialData, this.preserveState = true}); | |
@override | |
_FutureStateHook<T> createState() => _FutureStateHook<T>(); | |
} | |
class _FutureStateHook<T> extends HookState<AsyncSnapshot<T>, _FutureHook<T>> { | |
Object _activeCallbackIdentity; | |
AsyncSnapshot<T> _snapshot; | |
@override | |
void initHook() { | |
super.initHook(); | |
_snapshot = | |
AsyncSnapshot<T>.withData(ConnectionState.none, hook.initialData); | |
_subscribe(); | |
} | |
@override | |
void didUpdateHook(_FutureHook<T> oldHook) { | |
super.didUpdateHook(oldHook); | |
if (oldHook.future != hook.future) { | |
if (_activeCallbackIdentity != null) { | |
_unsubscribe(); | |
if (hook.preserveState) { | |
_snapshot = _snapshot.inState(ConnectionState.none); | |
} else { | |
_snapshot = | |
AsyncSnapshot<T>.withData(ConnectionState.none, hook.initialData); | |
} | |
} | |
_subscribe(); | |
} | |
} | |
@override | |
void dispose() { | |
_unsubscribe(); | |
} | |
void _subscribe() { | |
if (hook.future != null) { | |
final callbackIdentity = Object(); | |
_activeCallbackIdentity = callbackIdentity; | |
hook.future.then<void>((T data) { | |
if (_activeCallbackIdentity == callbackIdentity) { | |
setState(() { | |
_snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data); | |
}); | |
} | |
}, onError: (Object error) { | |
if (_activeCallbackIdentity == callbackIdentity) { | |
setState(() { | |
_snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error); | |
}); | |
} | |
}); | |
_snapshot = _snapshot.inState(ConnectionState.waiting); | |
} | |
} | |
void _unsubscribe() { | |
_activeCallbackIdentity = null; | |
} | |
@override | |
AsyncSnapshot<T> build(BuildContext context) { | |
return _snapshot; | |
} | |
} | |
AsyncSnapshot<T> useStream<T>(Stream<T> stream, | |
{T initialData, bool preserveState = true}) { | |
return Hook.use(_StreamHook( | |
stream, | |
initialData: initialData, | |
preserveState: preserveState, | |
)); | |
} | |
class _StreamHook<T> extends Hook<AsyncSnapshot<T>> { | |
final Stream<T> stream; | |
final T initialData; | |
final bool preserveState; | |
_StreamHook(this.stream, {this.initialData, this.preserveState = true}); | |
@override | |
_StreamHookState<T> createState() => _StreamHookState<T>(); | |
} | |
class _StreamHookState<T> extends HookState<AsyncSnapshot<T>, _StreamHook<T>> { | |
StreamSubscription<T> _subscription; | |
AsyncSnapshot<T> _summary; | |
@override | |
void initHook() { | |
super.initHook(); | |
_summary = initial(); | |
_subscribe(); | |
} | |
@override | |
void didUpdateHook(_StreamHook<T> oldWidget) { | |
super.didUpdateHook(oldWidget); | |
if (oldWidget.stream != hook.stream) { | |
if (_subscription != null) { | |
_unsubscribe(); | |
if (hook.preserveState) { | |
_summary = afterDisconnected(_summary); | |
} else { | |
_summary = initial(); | |
} | |
} | |
_subscribe(); | |
} | |
} | |
@override | |
void dispose() { | |
_unsubscribe(); | |
} | |
void _subscribe() { | |
if (hook.stream != null) { | |
_subscription = hook.stream.listen((T data) { | |
setState(() { | |
_summary = afterData(_summary, data); | |
}); | |
}, onError: (Object error) { | |
setState(() { | |
_summary = afterError(_summary, error); | |
}); | |
}, onDone: () { | |
setState(() { | |
_summary = afterDone(_summary); | |
}); | |
}); | |
_summary = afterConnected(_summary); | |
} | |
} | |
void _unsubscribe() { | |
if (_subscription != null) { | |
_subscription.cancel(); | |
_subscription = null; | |
} | |
} | |
@override | |
AsyncSnapshot<T> build(BuildContext context) { | |
return _summary; | |
} | |
AsyncSnapshot<T> initial() => | |
AsyncSnapshot<T>.withData(ConnectionState.none, hook.initialData); | |
AsyncSnapshot<T> afterConnected(AsyncSnapshot<T> current) => | |
current.inState(ConnectionState.waiting); | |
AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) { | |
return AsyncSnapshot<T>.withData(ConnectionState.active, data); | |
} | |
AsyncSnapshot<T> afterError(AsyncSnapshot<T> current, Object error) { | |
return AsyncSnapshot<T>.withError(ConnectionState.active, error); | |
} | |
AsyncSnapshot<T> afterDone(AsyncSnapshot<T> current) => | |
current.inState(ConnectionState.done); | |
AsyncSnapshot<T> afterDisconnected(AsyncSnapshot<T> current) => | |
current.inState(ConnectionState.none); | |
} | |
StreamController<T> useStreamController<T>( | |
{bool sync = false, | |
VoidCallback onListen, | |
VoidCallback onCancel, | |
List<Object> keys}) { | |
return Hook.use(_StreamControllerHook( | |
onCancel: onCancel, | |
onListen: onListen, | |
sync: sync, | |
keys: keys, | |
)); | |
} | |
class _StreamControllerHook<T> extends Hook<StreamController<T>> { | |
final bool sync; | |
final VoidCallback onListen; | |
final VoidCallback onCancel; | |
const _StreamControllerHook( | |
{this.sync = false, this.onListen, this.onCancel, List<Object> keys}) | |
: super(keys: keys); | |
@override | |
_StreamControllerHookState<T> createState() => | |
_StreamControllerHookState<T>(); | |
} | |
class _StreamControllerHookState<T> | |
extends HookState<StreamController<T>, _StreamControllerHook<T>> { | |
StreamController<T> _controller; | |
@override | |
void initHook() { | |
super.initHook(); | |
_controller = StreamController.broadcast( | |
sync: hook.sync, | |
onCancel: hook.onCancel, | |
onListen: hook.onListen, | |
); | |
} | |
@override | |
void didUpdateHook(_StreamControllerHook<T> oldHook) { | |
super.didUpdateHook(oldHook); | |
if (oldHook.onListen != hook.onListen) { | |
_controller.onListen = hook.onListen; | |
} | |
if (oldHook.onCancel != hook.onCancel) { | |
_controller.onCancel = hook.onCancel; | |
} | |
} | |
@override | |
StreamController<T> build(BuildContext context) { | |
return _controller; | |
} | |
@override | |
void dispose() { | |
_controller.close(); | |
} | |
} | |
bool debugHotReloadHooksEnabled = true; | |
@immutable | |
abstract class Hook<R> { | |
const Hook({this.keys}); | |
static R use<R>(Hook<R> hook) { | |
assert(HookElement._currentContext != null, ''' | |
`Hook.use` can only be called from the build method of HookWidget. | |
Hooks should only be called within the build method of a widget. | |
Calling them outside of build method leads to an unstable state and is therefore prohibited. | |
'''); | |
return HookElement._currentContext._use(hook); | |
} | |
final List<Object> keys; | |
static bool shouldPreserveState(Hook hook1, Hook hook2) { | |
final p1 = hook1.keys; | |
final p2 = hook2.keys; | |
if (p1 == p2) { | |
return true; | |
} | |
if ((p1 != p2 && (p1 == null || p2 == null)) || p1.length != p2.length) { | |
return false; | |
} | |
var i1 = p1.iterator; | |
var i2 = p2.iterator; | |
while (true) { | |
if (!i1.moveNext() || !i2.moveNext()) { | |
return true; | |
} | |
if (i1.current != i2.current) { | |
return false; | |
} | |
} | |
} | |
@protected | |
HookState<R, Hook<R>> createState(); | |
} | |
abstract class HookState<R, T extends Hook<R>> { | |
@protected | |
BuildContext get context => _element.context; | |
State _element; | |
T get hook => _hook; | |
T _hook; | |
@protected | |
void initHook() {} | |
@protected | |
void dispose() {} | |
@protected | |
void didBuild() {} | |
@protected | |
R build(BuildContext context); | |
@protected | |
void didUpdateHook(T oldHook) {} | |
void reassemble() {} | |
@protected | |
void setState(VoidCallback fn) { | |
_element.setState(fn); | |
} | |
} | |
class HookElement extends StatefulElement { | |
static HookElement _currentContext; | |
HookElement(HookWidget widget) : super(widget); | |
Iterator<HookState> _currentHook; | |
int _hookIndex; | |
List<HookState> _hooks; | |
bool _didFinishBuildOnce = false; | |
bool _debugDidReassemble; | |
bool _debugShouldDispose; | |
bool _debugIsInitHook; | |
@override | |
HookWidget get widget => super.widget as HookWidget; | |
@override | |
Widget build() { | |
_currentHook = _hooks?.iterator; | |
_currentHook?.moveNext(); | |
_hookIndex = 0; | |
assert(() { | |
_debugShouldDispose = false; | |
_debugIsInitHook = false; | |
_debugDidReassemble ??= false; | |
return true; | |
}()); | |
HookElement._currentContext = this; | |
final result = super.build(); | |
HookElement._currentContext = null; | |
assert(() { | |
if (!debugHotReloadHooksEnabled) return true; | |
if (_debugDidReassemble && _hooks != null) { | |
for (var i = _hookIndex; i < _hooks.length;) { | |
_hooks.removeAt(i).dispose(); | |
} | |
} | |
return true; | |
}()); | |
assert(_hookIndex == (_hooks?.length ?? 0), ''' | |
Build for $widget finished with less hooks used than a previous build. | |
Used $_hookIndex hooks while a previous build had ${_hooks.length}. | |
This may happen if the call to `Hook.use` is made under some condition. | |
'''); | |
assert(() { | |
if (!debugHotReloadHooksEnabled) return true; | |
_debugDidReassemble = false; | |
return true; | |
}()); | |
_didFinishBuildOnce = true; | |
return result; | |
} | |
@visibleForTesting | |
List<HookState> get debugHooks => List<HookState>.unmodifiable(_hooks); | |
@override | |
InheritedWidget inheritFromWidgetOfExactType(Type targetType, | |
{Object aspect}) { | |
assert(!_debugIsInitHook); | |
return super.inheritFromWidgetOfExactType(targetType, aspect: aspect); | |
} | |
@override | |
Element updateChild(Element child, Widget newWidget, dynamic newSlot) { | |
if (_hooks != null) { | |
for (final hook in _hooks.reversed) { | |
try { | |
hook.didBuild(); | |
} catch (exception, stack) { | |
FlutterError.reportError(FlutterErrorDetails( | |
exception: exception, | |
stack: stack, | |
library: 'hooks library', | |
context: DiagnosticsNode.message( | |
'while calling `didBuild` on ${hook.runtimeType}'), | |
)); | |
} | |
} | |
} | |
return super.updateChild(child, newWidget, newSlot); | |
} | |
@override | |
void unmount() { | |
super.unmount(); | |
if (_hooks != null) { | |
for (final hook in _hooks) { | |
try { | |
hook.dispose(); | |
} catch (exception, stack) { | |
FlutterError.reportError(FlutterErrorDetails( | |
exception: exception, | |
stack: stack, | |
library: 'hooks library', | |
context: | |
DiagnosticsNode.message('while disposing ${hook.runtimeType}'), | |
)); | |
} | |
} | |
} | |
} | |
@override | |
void reassemble() { | |
super.reassemble(); | |
assert(() { | |
_debugDidReassemble = true; | |
if (_hooks != null) { | |
for (final hook in _hooks) { | |
hook.reassemble(); | |
} | |
} | |
return true; | |
}()); | |
} | |
R _use<R>(Hook<R> hook) { | |
HookState<R, Hook<R>> hookState; | |
if (_currentHook == null) { | |
assert(_debugDidReassemble || !_didFinishBuildOnce); | |
hookState = _createHookState(hook); | |
_hooks ??= []; | |
_hooks.add(hookState); | |
} else { | |
assert(() { | |
if (!debugHotReloadHooksEnabled) return true; | |
if (!_debugDidReassemble) { | |
return true; | |
} | |
if (!_debugShouldDispose && | |
_currentHook.current?.hook?.runtimeType == hook.runtimeType) { | |
return true; | |
} | |
_debugShouldDispose = true; | |
if (_currentHook.current != null) { | |
_hooks.remove(_currentHook.current..dispose()); | |
hookState = _insertHookAt(_hookIndex, hook); | |
} else { | |
hookState = _pushHook(hook); | |
} | |
return true; | |
}()); | |
if (!_didFinishBuildOnce && _currentHook.current == null) { | |
hookState = _pushHook(hook); | |
_currentHook.moveNext(); | |
} else { | |
assert(_currentHook.current != null); | |
assert(_debugTypesAreRight(hook)); | |
if (_currentHook.current.hook == hook) { | |
hookState = _currentHook.current as HookState<R, Hook<R>>; | |
_currentHook.moveNext(); | |
} else if (Hook.shouldPreserveState(_currentHook.current.hook, hook)) { | |
hookState = _currentHook.current as HookState<R, Hook<R>>; | |
_currentHook.moveNext(); | |
final previousHook = hookState._hook; | |
hookState | |
.._hook = hook | |
..didUpdateHook(previousHook); | |
} else { | |
hookState = _replaceHookAt(_hookIndex, hook); | |
_resetsIterator(hookState); | |
_currentHook.moveNext(); | |
} | |
} | |
} | |
_hookIndex++; | |
return hookState.build(this); | |
} | |
HookState<R, Hook<R>> _replaceHookAt<R>(int index, Hook<R> hook) { | |
_hooks.removeAt(_hookIndex).dispose(); | |
var hookState = _createHookState(hook); | |
_hooks.insert(_hookIndex, hookState); | |
return hookState; | |
} | |
HookState<R, Hook<R>> _insertHookAt<R>(int index, Hook<R> hook) { | |
var hookState = _createHookState(hook); | |
_hooks.insert(index, hookState); | |
_resetsIterator(hookState); | |
return hookState; | |
} | |
HookState<R, Hook<R>> _pushHook<R>(Hook<R> hook) { | |
var hookState = _createHookState(hook); | |
_hooks.add(hookState); | |
_resetsIterator(hookState); | |
return hookState; | |
} | |
bool _debugTypesAreRight(Hook hook) { | |
assert(_currentHook.current.hook.runtimeType == hook.runtimeType); | |
return true; | |
} | |
void _resetsIterator(HookState hookState) { | |
_currentHook = _hooks.iterator; | |
while (_currentHook.current != hookState) { | |
_currentHook.moveNext(); | |
} | |
} | |
HookState<R, Hook<R>> _createHookState<R>(Hook<R> hook) { | |
assert(() { | |
_debugIsInitHook = true; | |
return true; | |
}()); | |
final state = hook.createState() | |
.._element = this.state | |
.._hook = hook | |
..initHook(); | |
assert(() { | |
_debugIsInitHook = false; | |
return true; | |
}()); | |
return state; | |
} | |
} | |
abstract class HookWidget extends StatefulWidget { | |
const HookWidget({Key key}) : super(key: key); | |
@override | |
HookElement createElement() => HookElement(this); | |
@override | |
_HookWidgetState createState() => _HookWidgetState(); | |
@protected | |
Widget build(BuildContext context); | |
} | |
class _HookWidgetState extends State<HookWidget> { | |
@override | |
Widget build(BuildContext context) { | |
return widget.build(context); | |
} | |
} | |
BuildContext useContext() { | |
assert(HookElement._currentContext != null, | |
'`useContext` can only be called from the build method of HookWidget'); | |
return HookElement._currentContext; | |
} | |
class HookBuilder extends HookWidget { | |
final Widget Function(BuildContext context) builder; | |
const HookBuilder({ | |
@required this.builder, | |
Key key, | |
}) : assert(builder != null), | |
super(key: key); | |
@override | |
Widget build(BuildContext context) => builder(context); | |
} | |
T useValueListenable<T>(ValueListenable<T> valueListenable) { | |
return useListenable(valueListenable).value; | |
} | |
T useListenable<T extends Listenable>(T listenable) { | |
Hook.use(_ListenableHook(listenable)); | |
return listenable; | |
} | |
class _ListenableHook extends Hook<void> { | |
final Listenable listenable; | |
const _ListenableHook(this.listenable) : assert(listenable != null); | |
@override | |
_ListenableStateHook createState() => _ListenableStateHook(); | |
} | |
class _ListenableStateHook extends HookState<void, _ListenableHook> { | |
@override | |
void initHook() { | |
super.initHook(); | |
hook.listenable.addListener(_listener); | |
} | |
@override | |
void didUpdateHook(_ListenableHook oldHook) { | |
super.didUpdateHook(oldHook); | |
if (hook.listenable != oldHook.listenable) { | |
oldHook.listenable.removeListener(_listener); | |
hook.listenable.addListener(_listener); | |
} | |
} | |
@override | |
void build(BuildContext context) {} | |
void _listener() { | |
setState(() {}); | |
} | |
@override | |
void dispose() { | |
hook.listenable.removeListener(_listener); | |
} | |
} | |
ValueNotifier<T> useValueNotifier<T>([T intialData, List<Object> keys]) { | |
return Hook.use(_ValueNotifierHook( | |
initialData: intialData, | |
keys: keys, | |
)); | |
} | |
class _ValueNotifierHook<T> extends Hook<ValueNotifier<T>> { | |
final T initialData; | |
const _ValueNotifierHook({List<Object> keys, this.initialData}) | |
: super(keys: keys); | |
@override | |
_UseValueNotiferHookState<T> createState() => _UseValueNotiferHookState<T>(); | |
} | |
class _UseValueNotiferHookState<T> | |
extends HookState<ValueNotifier<T>, _ValueNotifierHook<T>> { | |
ValueNotifier<T> notifier; | |
@override | |
void initHook() { | |
super.initHook(); | |
notifier = ValueNotifier(hook.initialData); | |
} | |
@override | |
ValueNotifier<T> build(BuildContext context) { | |
return notifier; | |
} | |
@override | |
void dispose() { | |
notifier.dispose(); | |
} | |
} | |
abstract class Store<State, Action> { | |
State get state; | |
void dispatch(Action action); | |
} | |
typedef Reducer<State, Action> = State Function(State state, Action action); | |
Store<State, Action> useReducer<State extends Object, Action>( | |
Reducer<State, Action> reducer, { | |
State initialState, | |
Action initialAction, | |
}) { | |
return Hook.use(_ReducerdHook(reducer, | |
initialAction: initialAction, initialState: initialState)); | |
} | |
class _ReducerdHook<State, Action> extends Hook<Store<State, Action>> { | |
final Reducer<State, Action> reducer; | |
final State initialState; | |
final Action initialAction; | |
const _ReducerdHook(this.reducer, {this.initialState, this.initialAction}) | |
: assert(reducer != null); | |
@override | |
_ReducerdHookState<State, Action> createState() => | |
_ReducerdHookState<State, Action>(); | |
} | |
class _ReducerdHookState<State, Action> | |
extends HookState<Store<State, Action>, _ReducerdHook<State, Action>> | |
implements Store<State, Action> { | |
@override | |
State state; | |
@override | |
void initHook() { | |
super.initHook(); | |
state = hook.reducer(hook.initialState, hook.initialAction); | |
assert(state != null); | |
} | |
@override | |
void dispatch(Action action) { | |
final res = hook.reducer(state, action); | |
assert(res != null); | |
if (state != res) { | |
setState(() { | |
state = res; | |
}); | |
} | |
} | |
@override | |
Store<State, Action> build(BuildContext context) { | |
return this; | |
} | |
} | |
T usePrevious<T>(T val) { | |
return Hook.use(_PreviousHook(val)); | |
} | |
class _PreviousHook<T> extends Hook<T> { | |
_PreviousHook(this.value); | |
final T value; | |
@override | |
_PreviousHookState<T> createState() => _PreviousHookState(); | |
} | |
class _PreviousHookState<T> extends HookState<T, _PreviousHook<T>> { | |
T previous; | |
@override | |
void didUpdateHook(_PreviousHook<T> old) { | |
previous = old.value; | |
} | |
@override | |
T build(BuildContext context) => previous; | |
} | |
void useReassemble(VoidCallback callback) { | |
assert(() { | |
Hook.use(_ReassembleHook(callback)); | |
return true; | |
}()); | |
} | |
class _ReassembleHook extends Hook<void> { | |
final VoidCallback callback; | |
_ReassembleHook(this.callback) : assert(callback != null); | |
@override | |
_ReassembleHookState createState() => _ReassembleHookState(); | |
} | |
class _ReassembleHookState extends HookState<void, _ReassembleHook> { | |
@override | |
void reassemble() { | |
super.reassemble(); | |
hook.callback(); | |
} | |
@override | |
void build(BuildContext context) {} | |
} | |
T useMemoized<T>(T Function() valueBuilder, | |
[List<Object> keys = const <dynamic>[]]) { | |
return Hook.use(_MemoizedHook( | |
valueBuilder, | |
keys: keys, | |
)); | |
} | |
class _MemoizedHook<T> extends Hook<T> { | |
final T Function() valueBuilder; | |
const _MemoizedHook(this.valueBuilder, | |
{List<Object> keys = const <dynamic>[]}) | |
: assert(valueBuilder != null), | |
assert(keys != null), | |
super(keys: keys); | |
@override | |
_MemoizedHookState<T> createState() => _MemoizedHookState<T>(); | |
} | |
class _MemoizedHookState<T> extends HookState<T, _MemoizedHook<T>> { | |
T value; | |
@override | |
void initHook() { | |
super.initHook(); | |
value = hook.valueBuilder(); | |
} | |
@override | |
T build(BuildContext context) { | |
return value; | |
} | |
} | |
R useValueChanged<T, R>(T value, R valueChange(T oldValue, R oldResult)) { | |
return Hook.use(_ValueChangedHook(value, valueChange)); | |
} | |
class _ValueChangedHook<T, R> extends Hook<R> { | |
final R Function(T oldValue, R oldResult) valueChanged; | |
final T value; | |
const _ValueChangedHook(this.value, this.valueChanged) | |
: assert(valueChanged != null); | |
@override | |
_ValueChangedHookState<T, R> createState() => _ValueChangedHookState<T, R>(); | |
} | |
class _ValueChangedHookState<T, R> | |
extends HookState<R, _ValueChangedHook<T, R>> { | |
R _result; | |
@override | |
void didUpdateHook(_ValueChangedHook<T, R> oldHook) { | |
super.didUpdateHook(oldHook); | |
if (hook.value != oldHook.value) { | |
_result = hook.valueChanged(oldHook.value, _result); | |
} | |
} | |
@override | |
R build(BuildContext context) { | |
return _result; | |
} | |
} | |
typedef Dispose = void Function(); | |
void useEffect(Dispose Function() effect, [List<Object> keys]) { | |
Hook.use(_EffectHook(effect, keys)); | |
} | |
class _EffectHook extends Hook<void> { | |
final Dispose Function() effect; | |
const _EffectHook(this.effect, [List<Object> keys]) | |
: assert(effect != null), | |
super(keys: keys); | |
@override | |
_EffectHookState createState() => _EffectHookState(); | |
} | |
class _EffectHookState extends HookState<void, _EffectHook> { | |
Dispose disposer; | |
@override | |
void initHook() { | |
super.initHook(); | |
scheduleEffect(); | |
} | |
@override | |
void didUpdateHook(_EffectHook oldHook) { | |
super.didUpdateHook(oldHook); | |
if (hook.keys == null) { | |
if (disposer != null) { | |
disposer(); | |
} | |
scheduleEffect(); | |
} | |
} | |
@override | |
void build(BuildContext context) {} | |
@override | |
void dispose() { | |
if (disposer != null) { | |
disposer(); | |
} | |
} | |
void scheduleEffect() { | |
disposer = hook.effect(); | |
} | |
} | |
ValueNotifier<T> useState<T>([T initialData]) { | |
return Hook.use(_StateHook(initialData: initialData)); | |
} | |
class _StateHook<T> extends Hook<ValueNotifier<T>> { | |
final T initialData; | |
const _StateHook({this.initialData}); | |
@override | |
_StateHookState<T> createState() => _StateHookState(); | |
} | |
class _StateHookState<T> extends HookState<ValueNotifier<T>, _StateHook<T>> { | |
ValueNotifier<T> _state; | |
@override | |
void initHook() { | |
super.initHook(); | |
_state = ValueNotifier(hook.initialData)..addListener(_listener); | |
} | |
@override | |
void dispose() { | |
_state.dispose(); | |
} | |
@override | |
ValueNotifier<T> build(BuildContext context) { | |
return _state; | |
} | |
void _listener() { | |
setState(() {}); | |
} | |
} | |
class _TextEditingControllerHookCreator { | |
const _TextEditingControllerHookCreator(); | |
TextEditingController call({String text, List<Object> keys}) { | |
return Hook.use(_TextEditingControllerHook(text, null, keys)); | |
} | |
TextEditingController fromValue(TextEditingValue value, [List<Object> keys]) { | |
return Hook.use(_TextEditingControllerHook(null, value, keys)); | |
} | |
} | |
const useTextEditingController = _TextEditingControllerHookCreator(); | |
class _TextEditingControllerHook extends Hook<TextEditingController> { | |
final String initialText; | |
final TextEditingValue initialValue; | |
_TextEditingControllerHook(this.initialText, this.initialValue, | |
[List<Object> keys]) | |
: assert( | |
initialText == null || initialValue == null, | |
"initialText and intialValue can't both be set on a call to " | |
'useTextEditingController!', | |
), | |
super(keys: keys); | |
@override | |
_TextEditingControllerHookState createState() { | |
return _TextEditingControllerHookState(); | |
} | |
} | |
class _TextEditingControllerHookState | |
extends HookState<TextEditingController, _TextEditingControllerHook> { | |
TextEditingController _controller; | |
@override | |
void initHook() { | |
if (hook.initialValue != null) { | |
_controller = TextEditingController.fromValue(hook.initialValue); | |
} else { | |
_controller = TextEditingController(text: hook.initialText); | |
} | |
} | |
@override | |
TextEditingController build(BuildContext context) => _controller; | |
@override | |
void dispose() => _controller?.dispose(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/animation.dart'; | |
import 'package:flutter/material.dart'; | |
// import 'package:flutter_hooks/flutter_hooks.dart'; | |
class UseAnimationControllerExample extends HookWidget { | |
@override | |
Widget build(BuildContext context) { | |
final myAnimation = useAnimationController( | |
animationBehavior: AnimationBehavior.normal, | |
lowerBound: 0.0, | |
upperBound: 200.0, | |
duration: const Duration(milliseconds: 1500), | |
initialValue: 0.0) | |
..forward(); | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('useAnimationController'), | |
), | |
body: AnimatedBuilder( | |
animation: myAnimation, | |
builder: (BuildContext context, Widget w) { | |
return Center( | |
child: Icon(Icons.mood, size: myAnimation.value), | |
); | |
})); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment