Slide 1

Slide 1 text

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

Slide 5

Slide 5 text

useState Goodbye setState(() {}) @override Widget build(BuildContext context) { final counter = useState(0); return GestureDetector( child: Text(counter.value.toString()), onTap: () => counter.value++, ); }

Slide 6

Slide 6 text

useState Goodbye setState(() {}) @override Widget build(BuildContext context) { final counter = useState(0); return GestureDetector( child: Text(counter.value.toString()), onTap: () => counter.value++, ); }

Slide 7

Slide 7 text

useState Goodbye setState(() {}) @override Widget build(BuildContext context) { final counter = useState(0); return GestureDetector( child: Text(counter.value.toString()), onTap: () => counter.value++, ); }

Slide 8

Slide 8 text

useState Goodbye setState(() {}) @override Widget build(BuildContext context) { final counter = useState(0); return GestureDetector( child: Text(counter.value.toString()), onTap: () => counter.value++, ); }

Slide 9

Slide 9 text

useState Goodbye setState(() {}) @override Widget build(BuildContext context) { final counter = useState(0); return GestureDetector( child: Text(counter.value.toString()), onTap: () => counter.value++, ); }

Slide 10

Slide 10 text

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(); }

Slide 24

Slide 24 text

class _ExampleState extends State with SingleTickerProviderStateMixin { AnimationController? _controller; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(seconds: 1)); } @override void dispose() { _controller!.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container(); } }

Slide 25

Slide 25 text

class _ExampleState extends State with SingleTickerProviderStateMixin { AnimationController? _controller; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(seconds: 1)); } @override void dispose() { _controller!.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container(); } }

Slide 26

Slide 26 text

class _ExampleState extends State with SingleTickerProviderStateMixin { AnimationController? _controller; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(seconds: 1)); } @override void dispose() { _controller!.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container(); } }

Slide 27

Slide 27 text

class _ExampleState extends State with SingleTickerProviderStateMixin { AnimationController? _controller; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(seconds: 1)); } @override void dispose() { _controller!.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container(); } }

Slide 28

Slide 28 text

AnimationController using Hooks

Slide 29

Slide 29 text

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(); }

Slide 52

Slide 52 text

@override void initHook() { time = Timer.periodic( Duration(seconds: 1), (timer) { setState(() { number = random.nextInt(9); }); } ); super.initHook(); } @override void dispose() { timer.cancel(); super.dispose(); }

Slide 53

Slide 53 text

@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