์๋ ๋ถํฐ ์งํํ๋ ๋ชจ์์์ ๋งค์ฃผ๋ง๋ค ์๋ก์ด ํจํค์ง์ ๋ํด์ ๊ฐ์ ๊ณต๋ถํด์ ๋ฐฐ์ด์ ์ ๊ณต์ ํ๋ ์๊ฐ์ ๊ฐ์ ธ๋ณด๊ธฐ๋ก ํ๋ค.
์ด๋ฒ์๋ flutter_hooks์ ๋ํด ์์๋ณด์๊ณ ์งํ์ด ๋์๊ณ ๋ด๊ฐ ๊ณต๋ถํ ๋ด์ฉ์ ์ ๋ฆฌํด๋ณด์๊ณ ํ๋ค.
hooks์ ๋ํ ๋ด์ฉ์ ๋ํ ์ฐธ๊ณ ๋ ์๋์ ๊ฐ๋ค.
pub.dev ๊ณต์
https://pub.dev/packages/flutter_hooks
๊ฐ๋ฐํ๋๋จ์๋ ์ ํ๋ธ
https://www.youtube.com/watch?v=GBVBLKESogU&list=PLgRxBCVPaZ_32XE17iRqS0y6cq3tQii48
๊ทธ๋ ๋ค๋ฉด hooks์ด๋ ๋ฌด์์ผ๊น?
์ผ๋จ hook์ด๋ ๊ฐ๊ณ ๋ฆฌ๋ ๋ป์ด๋ค. ์ฐพ์๋ณด๊ธฐ ์ด ๊ฐ๋ ์ React์์๋ถํฐ ์ฒ์ ๋์จ ๊ฒ ๊ฐ๋ค.
์ผ๋จ ํ๋ก๊ทธ๋๋ฐ์ธ์ด์์ ํ ์ด๋ '์๋ ์กด์ฌํ๋ ์ด๋ค ๊ธฐ๋ฅ์ ๊ฐ๊ณ ๋ฆฌ๋ฅผ ๊ฑฐ๋ ๊ฒ์ฒ๋ผ ๋ผ์ด๋ค์ด๊ฐ ๊ฐ์ด ์ํ' ํ๋ ์๋ฏธ๋ผ๊ณ ํ๋ค.
๊ทธ๋ ๋ค๋ฉด ๊ฒฐ๊ตญ ํ ํจํค์ง๋ dart์ฝ๋๋ฅผ ๊ฐ๋ฐ์๊ฐ ๊ฐ๊ฒฐํ๊ณ ์ฝ๊ฒ ๋ง๋ค๊ฒ ๋์์ฃผ๋ ๋ณด์กฐ์ญํ ์๋จ์ด๋ผ ์๊ฐํ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
์ค์ ํ ๊ฐ๋ฐ์๋ค์ ๊ณต์๋ฌธ์๋ฅผ ์ดํด๋ณด๋ฉด
'์ค๋ณต์ ์ ๊ฑฐํ๊ณ ์์ ฏ๊ฐ์ ์ฝ๋ ์์ฐ์ฑ์ ์ฆ๊ฐ์ํค๊ธฐ ์ํจ์ด๋ค.' ๋ผ๊ณ ์ ํ์๋ค.
์ฆ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋(Boiler Plate code)๋ฅผ ์ต๋ํ ์ค์ด๊ธฐ ์ํด ์ด ํจํค์ง๋ฅผ ๋ง๋ค์๋ค๊ณ ์ดํดํ๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.
์ค์ ์ฝ๋๋ฅผ ๋ณด๋ฉด์ ํ ์ ์ ์ฉํจ์ ๋ณด์!
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();
}
}
์ ์ฝ๋๋ ํ๋ฌํฐ์์ ์ ๋๋ฉ์ด์ ์ ๊ตฌํํ๊ธฐ ์ํ ๊ฐ๋จํ ์์ ์ด๋ค.
์ ๋๋ฉ์ด์ ์ ๊ตฌํํ๊ธฐ ์ํด์๋
initState(){} // ์ด๊ธฐ ์ค์
didUpdateWidget(){} // ์
๋ฐ์ดํธ ์ค์
dispose(){} // ๋ฉ๋ชจ๋ฆฌ์์ ์ ๊ฑฐ
์ด๋ ๊ฒ 3๊ฐ์ ๊ตฌ์ฑ์์๋ฅผ ๊ตฌํํ์ฌ ์๋ช ์ฃผ๊ธฐ(LifeCycle)์ ์๊ฐํ๋ฉด์ ๊ตฌํํด์ผ ๋๋ค.
๊ฐ๋ฐ์ ํ๋ฉด์ ์ฌ์ค ์๋ช ์ฃผ๊ธฐ๋ฅผ ์ ๊ฒฝ์ฐ์ง ์๊ณ (์๋ฅผ๋ค์ด ์ปจํธ๋กค๋ฌ๋ฅผ ํ ๋นํ๊ณ dispose()๋ฅผ ํ์ง ์๊ณ ๋ค๋ฅธ ํ๋ฉด์ผ๋ก ์ ํํ๋ค๋๊ฐ ๋ฑ)
์ด๋ ๊ฒ ๊ฐ๋ฐ์ ํ๋๋ฐ ์์ ๊ท๋ชจ์ ํ๋ก์ ํธ๋ผ๋ฉด ๊ด์ฐฎ์์ง ๋ชฐ๋ผ์ ํฐ ๊ท๋ชจ๋ผ๋ฉด ๋ถ๋ช ํ ๋ฉ๋ชจ๋ฆฌ์ ์ธ๋ชจ์๋ ๋ญ๋น๊ฐ ์์ ๊ฒ์ด๊ณ ์ข์ ์ฝ๋๋ผ ๋ณผ ์๋ ์์ ๊ฒ์ด๋ค.
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();
}
}
ํ์ง๋ง ์์๊ฐ์ด ํ ์ ์ด๋ค๋ฉด ์งง์ ์ฝ๋๋ก๋ ๋๊ฐ์ ์ฑ๋ฅ์ ๋ผ ์ ์๋ค.
์ค์ ์ ๋ ์ฝ๋๋ 100% ๋๊ฐ์ ๋์๋ฐฉ์์ผ๋ก ์คํ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ๋ค๋ฅธ ๋ก์ง๋ค์ ์ด๋์ ์๋๊ฒ์ธ๊ฐ? ๋ผ๊ณ ์๊ฐ์ด ๋ค ์ ์๋ค.
์ด๋ ํ ์ ๋ค ๊ตฌํ์ด ๋์ด์๊ธฐ์ ์ฐ๋ฆฌ๋ ๊ตฌํ๋ ๊ธฐ๋ฅ๋ค๋ง ๊ฐ์ ธ๋ค๊ฐ ์ฐ๋ฉด ๋๋ค.
์ด์ ์ด๋ค ๊ธฐ๋ฅ๋ค์ด ์๋์ง ๊ฐ๋จํ๊ฒ ์์๋ณด์.
No. | Name | Description |
1 | useState | ๋ณ์๋ฅผ ์์ฑํ๊ณ ๊ตฌ๋ ํฉ๋๋ค. |
2 | useEffect | ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๊ฑฐ๋ ์ ํ์ ์ผ๋ก ์ทจ์ํ๊ธฐ์ ์ ์ฉํฉ๋๋ค. |
3 | useMemoized | ๋ค์ํ ๊ฐ์ฒด์ ์ธ์คํด์ค๋ฅผ ์บ์ฑํฉ๋๋ค. |
4 | useStream | Stream์ ๊ตฌ๋ ํฉ๋๋ค. AsyncSnapShot์ผ๋ก ํ์ฌ์ํ๋ฅผ ๋ฐํํฉ๋๋ค. |
5 | useFuture | Future๋ฅผ ๊ตฌ๋ ํฉ๋๋ค. AsyncSnapShot์ผ๋ก ์ํ๋ฅผ ๋ฐํํฉ๋๋ค. |
6 | useAnimation | Animation์ ๊ตฌ๋ ํฉ๋๋ค. ํด๋น ๊ฐ์ฒด์ value๋ฅผ ๋ฐํํฉ๋๋ค. |
7 | useReducer | state๊ฐ ์กฐ๊ธ ๋ ๋ณต์กํ ๋, useState ๋์ ์ฌ์ฉํ ๋์์ ๋๋ค. |
8 | useTextEditingController | TextEditingController๋ฅผ ์์ฑํฉ๋๋ค. |
9 | useTabController | TabController๋ฅผ ์์ฑํฉ๋๋ค. |
10 | useScrollController | ScrollController๋ฅผ ์์ฑํฉ๋๋ค. |
11 | usePageController | ScrollController๋ฅผ ์์ฑํฉ๋๋ค. |
1. useState
์ ์ฝ๋๋ ๋ณ์๋ฅผ ์์ฑํ๊ณ ๊ตฌ๋ ์ ํ๋ค๊ณ ์ ํ์๋ค.
์ด๋ ๊ฐ๋จํ๊ฒ Getx์ .obs๋ผ๊ณ ์๊ฐํ๋ฉด ํธํ ๊ฒ ๊ฐ๋ค.
์ฆ ๋ด๊ฐ ๋ณ์๋ฅผ ์ ์ธํ๊ณ ๊ทธ ๋ณ์์ ์ํ๊ฐ ๋ณํ๊ฒ ๋๋ค๋ฉด ์๋์ผ๋ก ์ฌ๋น๋๋ฅผ ํด์ค๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค.
์ฆ, ์๋์ผ๋ก setState(){}๋ฅผ ํด์ฃผ๋ ์ญํ์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
class HomeScreen extends HookWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
final counter = useState(0);
return Scaffold(
appBar: AppBar(
title: const Text('useState'),
),
body: Center(
child: Text(
counter.value.toString(),
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => counter.value++,
child: const Icon(Icons.add),
),
);
}
}
์ ์ฝ๋๋ฅผ ๋ณด๋ฉด counter๋ณ์์ useState(0)๋ฅผ ํ ๋นํด ์ฃผ๊ณ ์๋ค.
์ด๋ ๊ฒ ๋๋ค๋ฉด 0์ด๋ผ๋ intํ์ ์๋์ผ๋ก ํ์ ์ ์ง์ ํด์ ์ด์ ๋ถํฐ ๋ณ์์ ์ํ๋ฅผ ๊ตฌ๋ ํ๊ฒ ๋๋ฏ๋ก ๋ณ๊ฒฝ์ฌํญ์ด ์์๋๋ง๋ค
Rebuild๋ฅผ ํ์ฌ ํ๋ฉด์ ์ํ๋ฅผ ์๋กญ๊ฒ ๋ฐ์ํ๊ฒ ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ๋๋ ๊ถ๊ธ์ฆ์ด ํ๋ ์๊ฒผ๋ค. Provider์ฒ๋ผ ํ๋ฉด์ ์ ๋ถ ๊ฐฑ์ ํ๋ ๊ฒ์ด ์๋๋ผ ๋ณ์ ๋ถ๋ถ๋ง ๊ฐฑ์ ํ๋ ๊ฒ์ธ๊ฐ?
๊ทธ๋์ log๋ฅผ ์ฐ์ด๋ณด๊ธฐ๋ก ํ๋ค.
๊ฒฐ๋ก ์ ํ๋ฉด์ ์ ๋ถ ์ฌ๋น๋ํ๊ฒ ๋๋ค.
์๋ ๊ทธ๋ ๋ค๋ฉด ๊ทธ๋ฅ Stateful ์์ ฏ์ setState๋ฅผ ์ฐ์ง ๊ท์ฐฎ๊ฒ ํจํค์ง ์ค์นํ๊ณ ์ด๊ฑฐ ๊ณต๋ถํด์ผ ํจ?
๋ผ๊ณ ์๊ฐ์ด ๋ค ์ ์๋ค. ๋ง๋ค ํ ์ ๊ทธ๋ฅ ๋์์ ์ฃผ๋ ํจํค์ง๋ผ ๊ฒฐ๊ตญ ๊ฐ์ธ์ด ์ ํํ๊ธฐ ๋๋ฆ์ด๋ค.
๊ทธ๋ ๋ค๋ฉด ํ ์์ด ์ฌ์ฉํด๋ณธ Stateful๋ก๋ง ์ ์ฝ๋๋ฅผ ๋ค์ ์์ฑํด๋ณด์.
๋ ์ฝ๋์ ์์ฐจ์ด๋ก ๋ดค์๋๋ hooks ํจํค์ง๋ฅผ ์ฌ์ฉํ๊ฒ ๋ ์งง๊ณ ๊ฐ๋ ์ฑ ์ธก๋ฉด์์๋ ์ข์ ๊ฒ ๊ฐ๋ค.
๊ทผ๋ฐ ๊ฒฐ๊ตญ ๊ฐ๋ฐ์ ๋ณธ์ธ์ด ํธํ์ชฝ์ผ๋ก ์ ํํ๋๊ฒ ์ข๋ค. ์ด๊ฒ ๋ ์ข๋ค๋ผ๊ณ ๋ฑ ์๋ผ ๋งํ ์๋ ์๋ค.
์์๊ฐํด๋ณด๊ณ ์ ๋ฌดํจ์จ์ด ์ฆ๊ฐํ๋ค๊ณ ์๊ฐ๋๋ ์ชฝ์ผ๋ก ์ ํํ๋๊ฒ ์ณ์ ์ ํ์ด๋ผ๊ณ ์๊ฐํ๋ค.
์ ์ฒด ์ฝ๋
์ฝ๋ ๋ณด๊ธฐ
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() {
runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
),
);
}
class HomeScreen extends HookWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
final counter = useState(0);
log('Rebuild');
return Scaffold(
appBar: AppBar(
title: const Text('useState'),
),
body: Center(
child: Text(
counter.value.toString(),
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => counter.value++,
child: const Icon(Icons.add),
),
);
}
}
// class HomeScreen extends StatefulWidget {
// const HomeScreen({super.key});
// @override
// State<HomeScreen> createState() => _HomeScreenState();
// }
// class _HomeScreenState extends State<HomeScreen> {
// int counter = 0;
// @override
// Widget build(BuildContext context) {
// log('Rebuild');
// return Scaffold(
// appBar: AppBar(
// title: const Text('useState'),
// ),
// body: Center(
// child: Text(
// counter.toString(),
// style: const TextStyle(
// fontSize: 32,
// fontWeight: FontWeight.bold,
// ),
// ),
// ),
// floatingActionButton: FloatingActionButton(
// onPressed: () => setState(() {
// counter++;
// }),
// child: const Icon(Icons.add),
// ),
// );
// }
// }
2. useEffect
๋ค์์ผ๋ก๋ useEffect์ ์ฌ์ฉ๋ฒ์ด๋ค.
useState์ useEffect ๋๊ฐ์ ์ฐจ์ด์ ์
useEffect๋ ์ ์ธ๋ ๋ฆฌ์คํธ๋ณ์์ ์ํ๊ฐ ๋ณํ ๋๋ง๋ค ํจ์๊ฐ ์คํ๋๋ค๋ ์ ์์ ์ฐจ์ด๊ฐ ์๋ค.
๊ธ๋ก๋ง ๋ณด๋ฉด ์ ํ ์ดํด๊ฐ ์๋๋๊ฒ ๋น์ฐํ๋ ์ฝ๋์ ์์ ๋ฅผ ๋ณด๋ฉด์ ์ฝ๊ฒ ์ดํดํด ๋ณด์!
์ผ๋จ useEffect๊ฐ ๋ฐ์์ผํ๋ ๋ฉ์๋๋ฅผ ํ์ธํด๋ณด์!
void useEffect(Dispose? Function() effect, [List<Object?>? keys]) {
use(_EffectHook(effect, keys));
}
(Function(), List<Object>) ์ด๋ ๊ฒ 2๊ฐ์ ๋ฉ์๋๊ฐ ํ์ํ๋ค.
์ฆ, ์ฒ์์๋ ํจ์์ ๊ฐ์ด ํ์ํ๊ณ ๊ทธ ๋ค์์ผ๋ก ๋ฆฌ์คํธ๊ฐ ์ฃผ์ด์ ธ์ผ ํ๋ค.
class HomeScreen extends HookWidget {
HomeScreen({super.key});
int increment = 0;
@override
Widget build(BuildContext context) {
final counter = useState(0);
// useEffect ์ฝ๋
useEffect(() {
log('useEffect ์คํ');
return () {};
}, [increment]);
...
Scaffold(..๊ทธ์ธ ์ฝ๋);
}
๊ฐ๋จํ๊ฒ ์ด๋ ๊ฒ ์๊ฐํ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
์ ์ฝ๋๋ useEffect๊ฐ ์คํ์ด ๋๋ค๋ฉด log๋ก 'useEffect ์คํ'์ด ํ๋ฆฐํธ ๋๊ณ ,
return๊ฐ์ผ๋ก๋ ์๋ฌด๊ฒ๋ ์ฃผ์ง ์๋๋ค. ๊ทธ๋ฆฌ๊ณ 2๋ฒ์งธ ์ธ์๋กค increment๋ผ๋ intํ์ ๋ฆฌ์คํธ ํํ๋ก ์ฃผ์๋ค.
์ ๊ทธ๋ ๋ค๋ฉด ์ด๋ป๊ฒ useEffect()๋ฅผ ์ฌ์ฉํด์ผ ํ๋์ง ์ ์ฒด ์ฝ๋๋ฅผ ๋ณด๋ฉด์ ์ดํดํด๋ณด์.
class HomeScreen extends HookWidget {
HomeScreen({super.key});
int increment = 0;
@override
Widget build(BuildContext context) {
final counter = useState(0);
// useEffect ๊ธฐ๋ฅ
useEffect(() {
log('useEffect ์คํ');
return () {};
}, [increment]);
log('Rebuild');
return Scaffold(
appBar: AppBar(
title: const Text('useState'),
),
body: Center(
child: Text(
counter.value.toString(),
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
counter.value++;
// counter๊ฐ์ด 5์ ๋ฐฐ์์ผ๋ increment 1์ฆ๊ฐ
if (counter.value % 5 == 0) {
increment++;
}
},
child: const Icon(Icons.add),
),
);
}
}
์ ์ฝ๋๋ฅผ ์์ธํ ๋ณด๋ฉด counter๊ฐ์ด 5์ ๋ฐฐ์์ผ๋ increment๊ฐ์ 1์ฉ ์ฆ๊ฐ ์ํจ๋ค.
์ด๋ increment์ ๊ฐ์ ๋ณํ๊ฐ ๊ฐ์ง๋๋ฉด useEffect()๊ฐ ์คํ์ด ๋๊ณ ๊ทธ ์์์๋ ํจ์๊ฐ ์คํ์ด ๋๋ค.
์์ ๊ฐ์ด ์ฒ์์ useEffect()๊ฐ ํ๋ฒ์ด ์คํ๋๊ณ
increment์ ๊ฐ์ ์ํ๊ฐ ๋ณํํ์ง ์๋๋ค๋ฉด useEffect๊ฐ ์คํ๋๋ ์ผ์ ์๋ค.
์ ๊ทธ๋ ๋ค๋ฉด ์๋ฌธ์ด ๋ค ์ ์๋ค.
์๋ ๊ทธ๋์ useState๋ useEffect๋ ๋๋์ฒด ๋ญ๊ฐ ๋ค๋ฅธ๊ฑด๋ฐ!?
๊ฐ๋จํ๊ฒ ์ค๋ช ํ์๋ฉด
useState๋ ์ํ๊ฐ ๋ณํํ ๋๋ง๋ค ์ ์ฒดํ๋ฉด์ ์ ๋ถ ์ฌ๋น๋ํ๊ฒ ๋๋ค.
ํ์ง๋ง useEffect๋ List[]์์ ์๋ ๋ณ์๋ค์ ํค๊ฐ์ด ๋ณํ ๋๋ง๋ค ์ํ๋ฅผ ๊ฐ์งํ์ฌ ํจ์๊ฐ ์คํ๋๋ค.
์ฌ์ฉ๋ฒ์ด ๋ง๋ ๋ฐฉ์์ธ์ง๋ ๋ชจ๋ฅด๊ฒ ์ง๋ง ์ด๋ ๊ฒ ์ฌ์ฉํ ์ ๋ ์์ ๊ฒ ๊ฐ๋ค.
๋ด๊ฐ ์ดํดํ ๋ด์ฉ์ ๊ทธ๋๋ก ์ฎ๊ฒจ ์ ์๋๋ฐ ์กฐ๊ธ์ด๋๋ง ์ดํด๊ฐ ๋์์ผ๋ฉด ์ข๊ฒ ๋ค.
ํน์ ์ถ๊ฐ๋ก ๊ถ๊ธํ ์ ์ด ์๋ค๋ฉด ๋๊ธ๋ก ๋จ๊ฒจ์ฃผ๊ธฐ ๋ฐ๋๋ค.
์์ธํ ๋ด์ฉ์ ๋ฐ์ ํ์ฝ๋๋ฅผ ์คํํด๋ณด๋ฉด์ ์ดํดํ๋ฉด ๋์ฑ ์ข์ ๊ฒ ๊ฐ๋ค.
https://dartpad.dev/?id=7face35a3c5eb5f24c66a92646754199
์ ์ฒด ์ฝ๋
์ฝ๋ ๋ณด๊ธฐ
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
),
);
}
// ignore: must_be_immutable
class HomeScreen extends HookWidget {
HomeScreen({super.key});
int increment = 0;
int increment1 = 0;
@override
Widget build(BuildContext context) {
final counter = useState(0);
// useEffect ๊ธฐ๋ฅ
useEffect(() {
log('useEffect ์คํ');
return () {
increment1++;
};
}, [increment]);
log('Rebuild');
return Scaffold(
appBar: AppBar(
title: const Text('useState'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
counter.value.toString(),
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),
),
Text(
increment1.toString(),
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
counter.value++;
// counter๊ฐ์ด 5์ ๋ฐฐ์์ผ๋ increment 1์ฆ๊ฐ
if (counter.value % 5 == 0) {
increment++;
}
},
child: const Icon(Icons.add),
),
);
}
}