FlutterでStateをもたせるには色々あるが、基本はStatefulWidgetを利用する。
StatefulWidgetは以下のようにStateを持ち、Stateの中で値を持ったりsetしたりできる。
StatefulWidget class - widgets library - Dart API
class Bird extends StatefulWidget {
const Bird({
Key? key,
this.color = const Color(0xFFFFE306),
this.child,
}) : super(key: key);
final Color color;
final Widget? child;
@override
_BirdState createState() => _BirdState();
}
class _BirdState extends State<Bird> {
double _size = 1.0;
void grow() {
setState(() { _size += 0.1; });
}
@override
Widget build(BuildContext context) {
return Container(
color: widget.color,
transform: Matrix4.diagonal3Values(_size, _size, 1.0),
child: widget.child,
);
}
}
他にもinitStateやdisposeなどWidgetが生成されるときや破棄されるタイミングに呼び出されるCallbackなどがある。
詳しくはドキュメントを読んでもらうとしてそんなのがある。
ちなみに逆にStatelessWidgetはStateを持たずinitStateなどもない。
StatefulWidgetの課題とは
initStateなどがあると言ったが、例えばテキストを入力できる画面を作りたいときにTextEditingControllerをinitStateで作って、disposeで破棄する処理を書いたりする。
TextEditingController class - widgets library - Dart API
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final TextEditingController _controller = TextEditingController();
@override
void initState() {
super.initState();
_controller.addListener(() {
final String text = _controller.text.toLowerCase();
_controller.value = _controller.value.copyWith(
text: text,
selection:
TextSelection(baseOffset: text.length, extentOffset: text.length),
composing: TextRange.empty,
);
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(6),
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(border: OutlineInputBorder()),
),
),
);
}
}
他にもScrollControllerだったりなんとかControllerがよくある。
そのたびに生成して破棄する処理を書く必要がある。
つまりTextEditingControllerに関する処理をinitStateやdisposeに書く必要があり関連するコードが一箇所にまとまらず分散してしまったり、何度も同じコードを書く必要がある。
解決方法1: mixinする
mixinする方法がある。
ただしmixinはTextEditingControllerが複数ある場合に対応出来ない。
解決方法2: Builderを用意する
TextEditingControllerBuilderみたいなWidgetを作って利用すればmixinの問題は解決するが、この方法はBuilderを使うのでbuilder外でControllerを使うことが難しくなる。
解決方法3: flutter_hooks
このように use*Controller
すればいいだけになる。解決方法1, 2で起きるような問題は起きない。
flutter_hooks | Flutter Package
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();
}
}
ただし、flutter_hooksはそれなりにデメリットもあると感じている。
- HookWidgetを継承する形になり一般的なStatelessWidgetを継承する形ではない
- useEffectなどを多用するとbuildメソッドが大きくなってしまう
use*
は if の中で使う事はできないが今の所機械的に発見しにくい
- Flutter公式が提供している機能ではない
イシューにはなっていないのか
イシューはある。
Static Metaprogrammingがあればいいみたいな話になっているがどうか
Reusing state logic is either too verbose or too difficult · Issue #51752 · flutter/flutter · GitHub
static metaprogramming自体導入されるかはまだ未定
language/intro.md at master · dart-lang/language · GitHub
hooksはFlutterに入らないのか
このイシューで色々と議論されたようですが今の所はなさそう。
Widget hooks · Issue #25280 · flutter/flutter · GitHub
まとめ
flutterh_ooksを使うのは良さげではあるのだが使う判断はまだ出来ておらずどうしたものかなーと思いながらStatefulWidgetを作っている。