Last active
December 16, 2024 11:59
-
-
Save temoki/aedf93fcc19ea97fc6b94622d53dc87a to your computer and use it in GitHub Desktop.
Open with DartPad ๐ https://dartpad.dev/?id=aedf93fcc19ea97fc6b94622d53dc87a
This file contains hidden or 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
//================================================================================ | |
// Flutter State Management Guide (2) | |
// | |
// ๅๅใฎ็ถใ | |
// https://gist.github.com/temoki/0e9acedbfa2b6ebe424f7e856dc2f5f3 | |
//================================================================================ | |
import 'dart:math' as math; | |
import 'package:flutter/material.dart'; | |
import 'package:hooks_riverpod/hooks_riverpod.dart'; | |
import 'package:flutter_hooks/flutter_hooks.dart'; | |
final items = <Widget>[ | |
Item((_) => PageA(), '[A] Widgetใใชใใซใใใใใฟใคใใณใฐ'), | |
Item((_) => PageB(), '[B] Widgetใฎใฉใคใใตใคใฏใซ'), | |
Item((_) => PageC(), '[C] Flutter Hooks ใฎๅบๆฌ (useState)'), | |
Item((_) => PageD(), '[D] Flutter Hooks ใฎๅบๆฌ (useEffect)'), | |
Item((_) => PageE(), '[E] Flutter Hooks ใฎๅบๆฌ (use~Controller)'), | |
Item((_) => PageF(), '[F] Riverpod ใฎๅๆฉ่ฝ'), | |
]; | |
//================================================================================ | |
// [A] Widget ใใชใใซใใใใใฟใคใใณใฐใซใคใใฆ | |
//================================================================================ | |
final class PageA extends StatelessWidget { | |
const PageA({super.key}); | |
@override | |
Widget build(BuildContext context) => CounterWidget(); | |
} | |
// (1) Countๅคใ็ถๆ ใซๆใคใใใใ StatefulWidget ใไพใซ่งฃ่ชฌใใฆใใใพใใ | |
final class CounterWidget extends StatefulWidget { | |
const CounterWidget({super.key}); | |
@override | |
State<CounterWidget> createState() => CounterState(); | |
} | |
final class CounterState extends State<CounterWidget> { | |
CounterState(); | |
int _count = 0; | |
@override | |
void initState() { | |
debugPrint('[CounterState] initState'); | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
debugPrint('[CounterState] build'); | |
return Center( | |
// (5) ใใฎ Rainbow ใฏ่ช่บซใใชใใซใใใใใจๆ ใฎ่ฒใๅคใใใใใซใชใฃใฆใใพใใ | |
// ๏ผใใฟใณใๆผใใใณใซ CounterWidget ใใชใใซใใใใใใฎ ๅญWidget ใซใไผๆฌใใพใใ | |
// Rainbow ใงๅฒใพใใ ๅWidget ใฎ่ฒใๅคใใใใจใง ๅญWidget ใฎใชใใซใใใใใใพใใ | |
child: Rainbow( | |
child: Container( | |
width: 200, | |
padding: const EdgeInsets.all(8), | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
mainAxisAlignment: MainAxisAlignment.center, | |
crossAxisAlignment: CrossAxisAlignment.stretch, | |
children: [ | |
// (6) ใใ ใใใใฎ Rainbow ใฏๆ ใฎ่ฒใๅคใใใพใใใ | |
// const๏ผ๏ผไธๅคใชใใฎ๏ผใจใใฆๅฎ็พฉใใใฆใใใใใงใใ | |
// ไธ่ฆใชใชใใซใใๆใใใใใซๅฏ่ฝใชใใฎใฏ const ใซใใใฎใ่ฏใใงใใ | |
const Rainbow( | |
child: Text('Count', textAlign: TextAlign.center), | |
), | |
// (7) ใใฎ Widget ใฏ Count ใจใใๅคๆฐใซไพๅญใใใฎใง const ใซใงใใพใใใ | |
Rainbow( | |
// (4) ใชใใซใๆใซๆดๆฐใใใ Countๅค ใไฝฟใใฎใง UI ไธใฎ Countๅค ใๆดๆฐใใใพใใ | |
child: Text('$_count', textAlign: TextAlign.center), | |
), | |
const SizedBox(height: 24), | |
Rainbow( | |
// (2) ใใฎ๏ผใใฟใณใๆผใใจ Countๅค ใใคใณใฏใชใกใณใใใใพใใ | |
child: FilledButton( | |
// (3) ใใฟใณๆผไธๆใซ setState() ใง Countๅค ใๆดๆฐใใฆใใพใใ | |
// StatefulWidget ใฏ setState() ใใใชใฌใผใซ่ช่บซใใชใใซใใใพใใ | |
onPressed: () => setState(() => _count++), | |
child: const Text('+'), | |
), | |
), | |
], | |
), | |
), | |
), | |
); | |
} | |
} | |
// (8) const ใซใงใใ Widget ใๅฎ็พฉใใใใใฎๆกไปถ | |
// ในใผใใผใฏใฉในใ const ใงใใ๏ผ้constใช NonConstClass ใซๅคๆดใใใ...๏ผ | |
class SomeClass extends ConstClass { | |
// ใณใณในใใฉใฏใฟใ const ใงใใ | |
const SomeClass({required this.value1, required this.value2}) : super(); | |
// ใในใฆใฎใใฃใผใซใใ final ใงใใ๏ผไธ้จใฎใใฃใผใซใใฎ final ใๅคใใจ...๏ผ | |
final int value1; | |
final int value2; | |
} | |
class ConstClass { | |
const ConstClass(); | |
} | |
class NonConstClass { | |
NonConstClass(); | |
} | |
//================================================================================ | |
// [B] Widgetใฎใฉใคใใตใคใฏใซ | |
//================================================================================ | |
// (1) ใใใใ Widget ใฏใใซใใฎใใณใซๆฐใใใคใณในใฟใณในใๅ็ๆใใใพใ๏ผconstใ้คใ๏ผใ | |
// ใชใใซใใง StatefulWidget ใๅ็ๆใใใฆใใใใฎ State ใฏ็ใ็ถใใฆใใใ | |
// ใงใฏใ่ชฐใ Widget ใฎใฉใคใใตใคใฏใซใ็ฎก็ใใฆใใใฎใงใใใใ๏ผ ๐ Element ใจใใใใฎใ | |
// ๅ ้จ็ใซใฏ Widgetใใชใผ ใจๅฏพใจใชใ Elementใใชใผ ใๆง็ฏใใใฆใใพใใ | |
// (ๅ่) https://docs.flutter.dev/resources/architectural-overview | |
// (2) Element ใฎๅฝนๅฒใฏ๏ผ | |
// ใปWidget ใจใใฎ็ถๆ ใฎใฉใคใใตใคใฏใซ็ฎก็ | |
// ใปใใซใ,ใชใใซใใฎ็ฎก็ | |
// ใปWidget ใฎ่ฆชๅญ้ขไฟใฎ็ฎก็ | |
// build ใกใฝใใใฎ BuildContext ใฏๅฎใฏ Element ใใฎใใฎใ ใฃใใใใพใใ | |
// (ๅ่) https://zenn.dev/chooyan/books/934f823764db62/viewer/3d3f8e | |
// (3) Widget ใใชใผใใใใ Widget ใใใชใใชใใจใใใฎๅฏพใจใชใ Element ใ็ ดๆฃใใใใใข | |
final class PageB extends StatefulWidget { | |
const PageB({super.key}); | |
@override | |
State<PageB> createState() => PageBState(); | |
} | |
final class PageBState extends State<PageB> { | |
PageBState(); | |
bool _flag = true; | |
@override | |
Widget build(BuildContext context) { | |
return Stack( | |
children: [ | |
// (4) Switch ใง _flag ใ true/false ใจๅใๆฟใใใใใซใชใฃใฆใใพใใ | |
Switch( | |
value: _flag, | |
onChanged: (newValue) => setState( | |
() => _flag = newValue, | |
), | |
), | |
// (5) ใใใฏ[A]ใฎ Counter ใจๅใใใฎใใใฟใณๆผไธใง Countๅค ใใคใณใฏใชใกใณใใ | |
// ใใ ใ_flag ใ false ใซใชใใจ Widget ใใชใผใใๆถใใใใใซใใฆใใใพใใ | |
// ๐ ๆถใใใจใจใใซ Element ใ็ ดๆฃใใใใใคใพใ State ใ็ ดๆฃใใใใ | |
if (_flag) const CounterWidget(), | |
// (6) ็ถๆ ใไฟๆใใใพใพ้่กจ็คบใซใใใๅ ดๅใฏ Visibility Widget ใไฝฟใใพใใใใ | |
// Visibility( | |
// visible: _flag, | |
// maintainState: true, // ใใฎใใฉใฐใ true ใซใใใใจใง็ถๆ ใไฟๆใใใพใ | |
// child: const CounterWidget(), | |
// ), | |
], | |
); | |
} | |
} | |
//================================================================================ | |
// [C] Flutter Hooks ใฎๅบๆฌ (useState) | |
//================================================================================ | |
// (1) Flutter Hooks ใฏใไธ่จใง่จใใจ StatefulWidget ใฎไปฃๆฟใจใชใใใฎใงใใ | |
// ใคใพใใWidget ใญใผใซใซใช็ถๆ ใฎ็ฎก็ใ่กใใใใฎใใฎใงใใ | |
// StatefulWidget ใใใใใชใใทใณใใซใซๅฎ่ฃ ใใใใจใใงใใพใใ | |
// ไพใจใใฆ StatefulWidget ใงๅฎ่ฃ ใใใ Counter ใ Flutter Hooks ใซ็ฝฎใๆใใฆใฟใพใใ | |
final class PageC extends StatelessWidget { | |
const PageC(); | |
@override | |
Widget build(BuildContext context) { | |
return const Center( | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
Text('StatefulWidget'), | |
CounterStatefulWidget(), | |
SizedBox(height: 32), | |
Text('Flutter Hooks'), | |
CounterHookWidget(), | |
], | |
), | |
); | |
} | |
} | |
// (2) StatefulWidget ใฎไพ | |
final class CounterStatefulWidget extends StatefulWidget { | |
const CounterStatefulWidget({super.key}); | |
@override | |
State<CounterStatefulWidget> createState() => CounterStatefulWidgetState(); | |
} | |
final class CounterStatefulWidgetState extends State<CounterStatefulWidget> { | |
CounterStatefulWidgetState(); | |
int _count = 0; | |
@override | |
void initState() => super.initState(); | |
@override | |
Widget build(BuildContext context) { | |
return Column(children: [ | |
Text('$_count'), | |
FilledButton( | |
onPressed: () => setState(() => _count++), | |
child: const Text('+'), | |
) | |
]); | |
} | |
} | |
// (3) Flutter Hooks ใฎไพใ | |
// HookWidget ใ็ถๆฟใใฆ Widget ใๅฎ็พฉใใใ ใใงใใState ใฎๅฎ็พฉใไธ่ฆใ | |
final class CounterHookWidget extends HookWidget { | |
const CounterHookWidget({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
// (4) build ใกใฝใใๅ ใง useState ใไฝฟใฃใฆ็ถๆ ใๅฎ็พฉใใพใใ | |
// ๅๅใใซใๆใฏๅผๆฐใงๆธกใใใๅๆๅคใจใชใใพใใ | |
final count = useState<int>(0); | |
return Column(children: [ | |
// (5) ็ถๆ ใฎๅคใๅ็ งใใใซใฏ value ใใฃใผใซใใๅ็ งใใพใใ | |
Text('${count.value}'), | |
FilledButton( | |
// (6) ็ถๆ ใฎๅคใๆดๆฐใใใซใฏ value ใใฃใผใซใใๆดๆฐใใใ ใใงใใ | |
// setState ใใชใใฆใใชใใซใใ็บ็ใใพใใ | |
onPressed: () => count.value++, | |
child: const Text('+'), | |
) | |
]); | |
} | |
} | |
// (7) ใชใ Hooks ใจ่จใใฎใ๏ผ | |
// UI๏ผWidget๏ผใซ็ถๆ ใใฒใฃใใใ๏ผใใใฏใใ๏ผใจใใๆๅณใใใใงใใ | |
// ๅฎฃ่จ็UI ใฎ็ฅใงใใ React ใใฌใผใ ใฏใผใฏใๅ ฌๅผใซๆไพใใๆฉ่ฝใ Flutter ใซใใฃใฆใใใ | |
// (ๅ่) https://ja.react.dev/reference/react/useState | |
// (8) useState ไปฅๅคใซใ use~ ใจใใๅๅใงไพฟๅฉใชใใใฏใ็จๆใใใฆใใใใ | |
// ่ชๅใง็ฌ่ชใฎใซในใฟใ ใใใฏใไฝใใใจใใงใใพใใ | |
// (ๅ่) https://github.com/rrousselGit/flutter_hooks#existing-hooks | |
//================================================================================ | |
// [D] Flutter Hooks ใฎๅบๆฌ (useEffect) | |
//================================================================================ | |
// (1) useEffect ใฏ Widget ใฎ็นๅฎใฎใใซใใฎใฟใคใใณใฐใงๅฆ็๏ผ= Effect๏ผใๅฎ่กใใใใใฎใใฎใงใใ | |
// ๆๅฎใใ keys ใๅๅใฎใใซใๆใใๅคๅใใๆใ ใ Effect ใๅฎ่กใใใพใใ | |
// ๅๅใใซใๆใ ใๅฆ็ใๅฎ่กใใใๆใชใฉใงใใๅฉ็จใใใพใ๏ผStatefulWidgetใฎinitStateใฎไปฃๆฟ๏ผ | |
final class PageD extends HookWidget { | |
const PageD({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
final count1 = useState<int>(0); | |
final count2 = useState<int>(0); | |
// (2) build ใกใฝใใใฎไธญใง useEffect ใๅผใณๅบใใพใใ | |
// ็นๅฎใฎใใซใใฎใฟใคใใณใฐใง็ฌฌ1ๅผๆฐใซๆๅฎใใ Function ใๅฎ่กใใใพใใ | |
// ใใใงใฏ็ฌฌ2ๅผๆฐ keys ใ็ฉบใชใฎใงๅๅใใซใๆใ ใๅฎ่กใใใพใใ | |
useEffect(() { | |
debugPrint('[PageD] useEffect'); | |
return null; | |
}, const []); | |
// (3) keys ใซๆๅฎใใใ count1 ใๅคๅใใๆใ ใๅฎ่กใใใพใใ | |
useEffect(() { | |
debugPrint('[PageD] useEffect for count1 => ${count1.value}'); | |
return null; | |
}, [count1.value]); | |
// (4) keys ใซๆๅฎใใใ count2 ใๅคๅใใๆใ ใๅฎ่กใใใพใใ | |
useEffect(() { | |
debugPrint('[PageD] useEffect for count2 => ${count2.value}'); | |
return null; | |
}, [count2.value]); | |
return Center( | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
Text("Count 1 = ${count1.value}"), | |
FilledButton( | |
onPressed: () => count1.value++, | |
child: const Text('+'), | |
), | |
const SizedBox(height: 8), | |
Text("Count 2 = ${count2.value}"), | |
FilledButton( | |
onPressed: () => count2.value++, | |
child: const Text('+'), | |
), | |
const SizedBox(height: 8) | |
], | |
), | |
); | |
} | |
} | |
//================================================================================ | |
// [E] Flutter Hooks ใฎๅบๆฌ (use~Controller) | |
//================================================================================ | |
// (1) use~Controller ใฏ Flutter ใฎ TextEditingController ใ AnimationController ใชใฉใ | |
// UI ใใณใณใใญใผใซใใใใใซๆไพใใใฆใใ Controller ใๆฑใใใใใใฆใใใพใใ | |
final class PageE extends HookWidget { | |
const PageE(); | |
@override | |
Widget build(BuildContext context) { | |
return Center( | |
child: Padding( | |
padding: EdgeInsets.all(32), | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
Text('StatefulWidget'), | |
TextFieldStatefulWidget(), | |
SizedBox(height: 32), | |
Text('Flutter Hooks'), | |
TextFieldHookWidget(), | |
], | |
), | |
), | |
); | |
} | |
} | |
// (2) StatefulWidget ใฎไพ | |
final class TextFieldStatefulWidget extends StatefulWidget { | |
const TextFieldStatefulWidget({super.key}); | |
@override | |
State<TextFieldStatefulWidget> createState() => | |
TextFieldStatefulWidgetState(); | |
} | |
final class TextFieldStatefulWidgetState | |
extends State<TextFieldStatefulWidget> { | |
TextFieldStatefulWidgetState(); | |
late final TextEditingController _controller; | |
@override | |
void initState() { | |
// (3) Controller ใฎ็ๆใฏ initState ใฎไธญใง | |
_controller = TextEditingController(text: 'initial text'); | |
super.initState(); | |
} | |
@override | |
void dispose() { | |
// (4) ไธ่ฆใซใชใฃใๆ็นใง dispose ใกใฝใใใๅผใใงใใใชใใใฐใชใใพใใ | |
_controller.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return TextField( | |
controller: _controller, | |
decoration: InputDecoration( | |
suffixIcon: IconButton( | |
icon: Icon(Icons.clear), | |
onPressed: () => _controller.text = '', | |
), | |
), | |
); | |
} | |
} | |
// (3) HookWidget + useTextEditingController ใฎไพ | |
final class TextFieldHookWidget extends HookWidget { | |
const TextFieldHookWidget({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
// (4) build ใกใฝใใๅ ใง useTextEditingController ใไฝฟใฃใฆ Controller ใๅๅพใใพใใ | |
// dispose ใจใใฃใใฉใคใใตใคใฏใซใฎ็ฎก็ใฏๅๆใซใใฃใฆใใใใฎใงไธ่ฆใงใใ | |
final controller = useTextEditingController(text: 'initial text'); | |
return TextField( | |
controller: controller, | |
decoration: InputDecoration( | |
suffixIcon: IconButton( | |
icon: Icon(Icons.clear), | |
onPressed: () => controller.text = '', | |
), | |
), | |
); | |
} | |
} | |
//================================================================================ | |
// [F] Riverpod ใฎๅๆฉ่ฝ | |
//================================================================================ | |
// ๅ ฌๅผใใญใฅใกใณใ | |
// https://dartpad.dev/?id=aedf93fcc19ea97fc6b94622d53dc87a | |
// | |
// ไป็ตใฟใใ็่งฃใใ Riverpod / Riverpod ใใผใใทใผใ | |
// https://zenn.dev/chooyan/books/92a0a489f68233/viewer/overview | |
final class PageF extends StatelessWidget { | |
const PageF(); | |
@override | |
Widget build(BuildContext context) { | |
return const Center( | |
child: Text('No contents'), | |
); | |
} | |
} | |
//================================================================================ | |
// Common | |
//================================================================================ | |
void main() => runApp(const App()); | |
final class App extends StatelessWidget { | |
const App({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return ProviderScope( | |
child: MediaQuery( | |
data: MediaQuery.of(context).copyWith( | |
textScaler: TextScaler.linear(2.0), | |
), | |
child: MaterialApp( | |
title: 'Flutter State Management Guide', | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData(colorSchemeSeed: Colors.blue), | |
home: Scaffold( | |
appBar: AppBar(title: Text('Flutter State Management')), | |
body: ListView(children: items), | |
), | |
), | |
), | |
); | |
} | |
} | |
final class Item extends StatelessWidget { | |
Item(this.builder, this.title, {super.key}); | |
final String title; | |
final WidgetBuilder builder; | |
@override | |
Widget build(BuildContext context) { | |
return ListTile( | |
title: Text(title), | |
onTap: () => Navigator.of(context).push( | |
MaterialPageRoute( | |
builder: (c) => Scaffold( | |
appBar: AppBar( | |
title: Text(title), | |
), | |
body: builder(c), | |
), | |
), | |
), | |
); | |
} | |
} | |
final class Rainbow extends StatelessWidget { | |
const Rainbow({required this.child, super.key}); | |
final Widget child; | |
@override | |
Widget build(BuildContext context) => RepaintBoundary( | |
key: super.key, | |
child: CustomPaint( | |
painter: _RepaintPainter(), | |
child: child, | |
), | |
); | |
} | |
final class _RepaintPainter extends CustomPainter { | |
_RepaintPainter(); | |
final _num = math.Random().nextInt((1 << 31) - 1); | |
@override | |
void paint(Canvas canvas, Size size) { | |
int k = 0; | |
int r, g, b = 0; | |
double lum = 0.0; | |
do { | |
k += 1; | |
r = (_num * k * 64) % 255; | |
g = (_num * k * 128) % 255; | |
b = (_num * k * 192) % 255; | |
lum = 0.2126 * (r / 255.0) + 0.7152 * (g / 255.0) + 0.0722 * (b / 255.0); | |
} while (lum > 0.8); | |
canvas.drawRect( | |
Offset(1, 1) & Size(size.width - 2, size.height - 2), | |
Paint() | |
..color = Color.fromARGB(255, r, g, b) | |
..style = PaintingStyle.stroke | |
..strokeWidth = 2, | |
); | |
} | |
@override | |
bool shouldRepaint(covariant CustomPainter oldDelegate) => true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment