Flutter Hooks
Samuel Dionne
Android Dev at Transit and
GDG Montreal Organizer
Reduce boilerplate
Slide 2
Slide 2 text
• What are hooks
• Using a hook
• Understanding hooks
• Creating a hook
Agenda
Explaining the magic
Slide 3
Slide 3 text
v
“A Flutter implementation of
React hooks.”
- pub.dev
Slide 4
Slide 4 text
Hooks are a way to share the same code
with multiple widgets, code that is usually
duplicated or hard to share between
stateful widgets.
– Jimmy Aumard
medium.com/flutter-community
useMemoized
Construct only once
@override
Widget build(BuildContext context) {
final expensiveObject = useMemoized(() { myExpensiveObject() });
return Text(expensiveObject.toString());
}
Slide 11
Slide 11 text
useMemoized
Construct only once
@override
Widget build(BuildContext context) {
final expensiveObject = useMemoized(() { myExpensiveObject() });
return Text(expensiveObject.toString());
}
Slide 12
Slide 12 text
useMemoized
Construct only once
@override
Widget build(BuildContext context) {
final expensiveObject = useMemoized(() { myExpensiveObject() });
return Text(expensiveObject.toString());
}
Slide 13
Slide 13 text
useMemoized
Construct only once
@override
Widget build(BuildContext context) {
final expensiveObject = useMemoized(() { myExpensiveObject() });
return Text(expensiveObject.toString());
}
Slide 14
Slide 14 text
useState + useMemoized
Update instance on key change
@override
Widget build(BuildContext context) {
final counter = useState(0);
final expensiveObject = useMemoized(() {
myExpensiveObject(counter.value)
}, [counter.value]);
return ...;
}
Slide 15
Slide 15 text
useState + useMemoized
Update instance on key change
@override
Widget build(BuildContext context) {
final counter = useState(0);
final expensiveObject = useMemoized(() {
myExpensiveObject(counter.value)
}, [counter.value]);
return ...;
}
Slide 16
Slide 16 text
useState + useMemoized
Update instance on key change
@override
Widget build(BuildContext context) {
final counter = useState(0);
final expensiveObject = useMemoized(() {
myExpensiveObject(counter.value)
}, [counter.value]);
return ...;
}
Slide 17
Slide 17 text
useState + useMemoized
Update instance on key change
@override
Widget build(BuildContext context) {
final counter = useState(0);
final expensiveObject = useMemoized(() {
myExpensiveObject(counter.value)
}, [counter.value]);
return ...;
}
Slide 18
Slide 18 text
useState + useMemoized
Update instance on key change
@override
Widget build(BuildContext context) {
final counter = useState(0);
final expensiveObject = useMemoized(() {
myExpensiveObject(counter.value)
}, [counter.value]);
return ...;
}
Slide 19
Slide 19 text
• Multiple animations
• Long form with lots of text fields
There are no limit to the number of hooks you
can define in your build method. Same type,
different type, no problems!
Multiple Hooks
Even with the same type
Slide 20
Slide 20 text
• useState
• useMemoized
• useAnimationController
• useTextEditingController
• useFocusNode
• useTabController
• useScrollController
• usePageController
Subset of the whole list
Existing Hooks
Slide 21
Slide 21 text
• Hard to write reusable code in State classes
• Lot of required steps (init, dispose) that are always the same
• Very verbose
Writing boilerplate does not get you more users
Problems with StatefulWidget
Slide 22
Slide 22 text
AnimationController
using StatefulWidget
Slide 23
Slide 23 text
class Example extends StatefulWidget {
@override
_ExampleState createState() => _ExampleState();
}
class Example extends HookWidget {
@override
Widget build(BuildContext context) {
final controller = useAnimationController(duration: Duration(seconds: 1));
return Container();
}
}
Slide 30
Slide 30 text
class Example extends HookWidget {
@override
Widget build(BuildContext context) {
final controller = useAnimationController(duration: Duration(seconds: 1));
return Container();
}
}
Slide 31
Slide 31 text
class Example extends HookWidget {
@override
Widget build(BuildContext context) {
final controller = useAnimationController(duration: Duration(seconds: 1));
return Container();
}
}
Slide 32
Slide 32 text
• Hooks are stored in the Element of a Widget just like State
• Unlike State which is unique, Hooks are stores in a List
• The list is indexed by the number of call to use
Or where did all the code go?
How do they work
Slide 33
Slide 33 text
class HookElement extends Element {
List _hooks;
int _hookIndex;
T use(Hook hook) => _hooks[_hookIndex++].build(this);
@override
void performRebuild() {
_hookIndex = 0;
super.performRebuild();
}
}
Slide 34
Slide 34 text
class HookElement extends Element {
List _hooks;
int _hookIndex;
T use(Hook hook) => _hooks[_hookIndex++].build(this);
@override
void performRebuild() {
_hookIndex = 0;
super.performRebuild();
}
}
Slide 35
Slide 35 text
class HookElement extends Element {
List _hooks;
int _hookIndex;
T use(Hook hook) => _hooks[_hookIndex++].build(this);
@override
void performRebuild() {
_hookIndex = 0;
super.performRebuild();
}
}
Slide 36
Slide 36 text
class HookElement extends Element {
List _hooks;
int _hookIndex;
T use(Hook hook) => _hooks[_hookIndex++].build(this);
@override
void performRebuild() {
_hookIndex = 0;
super.performRebuild();
}
}
Slide 37
Slide 37 text
class HookElement extends Element {
List _hooks;
int _hookIndex;
T use(Hook hook) => _hooks[_hookIndex++].build(this);
@override
void performRebuild() {
_hookIndex = 0;
super.performRebuild();
}
}
Slide 38
Slide 38 text
class HookElement extends Element {
List _hooks;
int _hookIndex;
T use(Hook hook) => _hooks[_hookIndex++].build(this);
@override
void performRebuild() {
_hookIndex = 0;
super.performRebuild();
}
}
Slide 39
Slide 39 text
• Only use Hooks in the build method
• No conditions around Hooks
• No Hooks in loops
Basically the states are valids as long as the
number of use calls is the same.
Some rules
Simple and easy to follow
Slide 40
Slide 40 text
@override
Widget build(BuildContext context) {
final loading = useState(true);
final error = useState(“”);
return ...
}
Slide 41
Slide 41 text
@override
Widget build(BuildContext context) {
final message = useState(“”);
final loading = useState(true);
final error = useState(“”);
return ...
}
Slide 42
Slide 42 text
@override
Widget build(BuildContext context) {
final message = useState(“”);
final loading = useState(true);
final error = useState(“”);
return ...
}
Slide 43
Slide 43 text
@override
Widget build(BuildContext context) {
final message = useState(“”);
final loading = useState(true);
if (!loading.value) {
final error = useState(“”);
...
}
final controller = useAnimationController(duration: Duration(seconds: 1));
return ...
}
Slide 44
Slide 44 text
Let’s create a
new Hook
Slide 45
Slide 45 text
class _RandomNumberGenerator extends Hook {
const _RandomNumberGenerator():
@override
State<_RandomNumberGenerator> createState() => _RandomNumberGeneratorState();
}
class _RandomNumberGeneratorState extends HookState {
@override
int build(BuildContext context) {
return 0;
}
}
Slide 46
Slide 46 text
class _RandomNumberGenerator extends Hook {
const _RandomNumberGenerator():
@override
State<_RandomNumberGenerator> createState() => _RandomNumberGeneratorState();
}
class _RandomNumberGeneratorState extends HookState {
@override
int build(BuildContext context) {
return 0;
}
}
Slide 47
Slide 47 text
class _RandomNumberGenerator extends Hook {
const _RandomNumberGenerator():
@override
State<_RandomNumberGenerator> createState() => _RandomNumberGeneratorState();
}
class _RandomNumberGeneratorState extends HookState {
@override
int build(BuildContext context) {
return 0;
}
}
Slide 48
Slide 48 text
class _RandomNumberGenerator extends Hook {
const _RandomNumberGenerator():
@override
State<_RandomNumberGenerator> createState() => _RandomNumberGeneratorState();
}
class _RandomNumberGeneratorState extends HookState {
@override
int build(BuildContext context) {
return 0;
}
}
Slide 49
Slide 49 text
class _RandomNumberGenerator extends Hook {
const _RandomNumberGenerator():
@override
State<_RandomNumberGenerator> createState() => _RandomNumberGeneratorState();
}
class _RandomNumberGeneratorState extends HookState {
@override
int build(BuildContext context) {
return 0;
}
}
Slide 50
Slide 50 text
class _RandomNumberGeneratorState extends HookState {
final Random random = Random();
late Timer timer;
int number = 0;
@override
void initHook() {
time = Timer.periodic(
Duration(seconds: 1),
(timer) { setState(() { number = random.nextInt(9); }); }
);
super.initHook();
}
Slide 51
Slide 51 text
class _RandomNumberGeneratorState extends HookState {
final Random random = Random();
late Timer timer;
int number = 0;
@override
void initHook() {
time = Timer.periodic(
Duration(seconds: 1),
(timer) { setState(() { number = random.nextInt(9); }); }
);
super.initHook();
}
@override
int build(BuildContext context) {
return number;
}
}
int useRandomNumber() {
return use(const _RandomNumberGenerator());
}
Slide 54
Slide 54 text
@override
int build(BuildContext context) {
return number;
}
}
int useRandomNumber() {
return use(const _RandomNumberGenerator());
}
Slide 55
Slide 55 text
class _RandomNumberGeneratorState extends HookState {
final Random random = Random();
late Timer timer;
int number = 0;
@override
void initHook() {
time = Timer.periodic(
Duration(seconds: 1),
(timer) { setState(() { number = random.nextInt(9); }); }
);
super.initHook();
}
@override
void dispose() {
timer.cancel();
super.dispose();
}
@override
int build(BuildContext context) {
return number;
}
}
Slide 56
Slide 56 text
@override
Widget build(BuildContext context) {
final random1 = useRandomNumber();
final random2 = useRandomNumber();
return ...;
}
Slide 57
Slide 57 text
Tips and Tricks
Avoiding headache
- Always put Hooks at the top of functions
- Keep the separation of concerns in mind
- Keep to Hooks as close as possible to the
code that uses them
Slide 58
Slide 58 text
Questions?
Slide 59
Slide 59 text
References:
• https://pub.dev/packages/flutter_hooks
• https://medium.com/flutter-community/fl
utter-hooks-7754df814995
• https://medium.com/flutter-community/fl
utter-hooks-say-goodbye-to-statefulwid
get-and-reduce-boilerplate-code-8573d
4720f9a
Thank you!
Happy to answer questions on
Slack