今日はFlutterのダイアログのお話。
ダイアログの中にテキストフィールドを準備して、バリデーション違反があったらエラーメッセージを追加したいということがありました。
ダイアログの表示自体はSimpleDialog
やAlertDialog
を使えばできるのですが、表示の変更ができずにハマりました。
以下がコード例。
簡単に、AppBar
にあるアイコンをタップするとダイアログが表示されて、ダイアログ内のボタンをタップすると表示をトグルするようなコードにしています。
class _AwesomePageState extends State<AwesomePage> { bool _isAwesome; String _awesomeText; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Everyday Awesome'), actions: <Widget>[ PopupMenuButton( onSelected: (String value) { showDialog( context: context, builder: (BuildContext context) => SimpleDialog( title: const Text('It says'), children: <Widget>[ const Text('Flutter is awesome.'), RaisedButton( child: const Text('u a Awesome!'), onPressed: () { setState(() { _isAwesome = !_isAwesome; _awesomeText = _isAwesome ? 'u a Awesome!' : 'please tap Awesome..'; }); }, ), ], ), ); }, itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[ const PopupMenuItem(child: const Text('A'), value: 'a'), ], ) ], ), body: Center( child: const Text('Awesome'), ), ); } }
ボタンをタップする度にonPressed
の中でsetState()
して_awesomeText
が変更されて「u a Awesome!」となるはずですが、何度タップしても変更されません。
ボタンだけでなく、タイトルやテキストを変更しようとしても同じ。
おかしいな、と思ってSimpleDialog
のコードを読んでみると、理由がわかりました。
class SimpleDialog extends StatelessWidget { // ...(略) }
StatelessWidget!!
setState()
しても意味ないはずでした!
念の為AlertDialog
やDialog
クラスを見てみても、どちらも同じくStatelessWidget
。
ガイドラインをそう読み込んだわけではないのですが、Material Designガイドラインを読むに、ダイアログはあくまでも警告・エラーを表示したり、選択可能なリストを表示したりといったすごくシンプルなUIを目的としているみたい。
じゃあどうすれば…というと、FlutterによるFull-Screen Dialogの実装例があるのを先輩に教えて頂きました。
ちょっと長いですが、要は、StatefulWidget
を作って、SimpleDialog
の代わりに呼び出す、ということのよう。
実装するとこんな感じになりました。
class _AwesomePageState extends State<AwesomePage> { bool _isAwesome; String _awesomeText; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Everyday Awesome'), actions: <Widget>[ PopupMenuButton( onSelected: (String value) { showDialog( context: context, builder: (_) { return AwesomeDialog(); }, ); }, itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[ const PopupMenuItem(child: const Text('A'), value: 'あ'), ], ) ], ), body: Center( child: const Text('Awesome'), ), ); } } class AwesomeDialog extends StatefulWidget { @override _AwesomeDialogState createState() => _AwesomeDialogState(); } class _AwesomeDialogState extends State<AwesomeDialog> { bool _isAwesome; String _awesomeText; @override void initState() { super.initState(); _isAwesome = false; _awesomeText = 'please tap Awesome..'; } Widget _buildAwesomeButton() { return RaisedButton( child: Text(_awesomeText), onPressed: () { setState(() { _isAwesome = !_isAwesome; _awesomeText = _isAwesome ? 'u a Awesome!' : 'please tap Awesome..'; }); }, ); } @override Widget build(BuildContext context) { return SimpleDialog( title: const Text('It says'), children: <Widget>[ const Text('Flutter is awesome.'), _buildAwesomeButton(), ], ); } }
showDialog
関数のbuilder
にSimpleDialog
ではなく、AwesomeDialog
を渡しています。
AwesomeDialog
も返すウィジェットはSimpleDialog
ですが、stateを持ったRaisedButton
を外だしにして渡すことで、stateの変更が反映されるようになりました。
では、最初の例でもRaisedButton
を外だししたり、stateにしてしまえばよいのでは?🤔と思ったけれど、これはうまくいかない。
この辺りはまたいずれ調べてみようと思います!