null safety article
Contents
APIドキュメント
https://api.flutter.dev/flutter/material/PopupMenuButton-class.html
サンプル
ページ遷移(サンプル1)
//PopupMenuButtonのサンプル1(ページ遷移) import 'package:flutter/material.dart'; void main() => runApp( MaterialApp( home: MyApp(), ), ); void pushPage(BuildContext context, Widget page) { Navigator.of(context).push( MaterialPageRoute<void>(builder: (_) => page), ); } List<Function> listOfRoute = [ (BuildContext context) => pushPage( context, FirstPage(), ), (BuildContext context) => pushPage( context, SecondPage(), ), (BuildContext context) => pushPage( context, ThirdPage(), ), ]; class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( actions: [ PopupMenuButton<int>( itemBuilder: (context) { return const [ PopupMenuItem( value: 0, child: Text("0番目"), ), PopupMenuItem( value: 1, child: Text("1番目"), ), PopupMenuItem( value: 2, child: Text("2番目"), ), ]; }, onSelected: (value) { listOfRoute[value]( context, ); }, ), ], ), ); } } class FirstPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar:AppBar(), body: Container( color: Colors.purpleAccent, ), ); } } class SecondPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar:AppBar(), body: Container( color: Colors.yellow, ), ); } } class ThirdPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar:AppBar(), body: Container( color: Colors.blue, ), ); } }
ページ遷移の例です。itemBuilderパラメータはcontextを引数にとり、Listを返す関数ですね。はい。
それぞれのPopupMenuItemにvalueを設定しますね。はい。そのvalueの型がPopupMenuItemの型引数になります。上記サンプルではint型なのでPopupMenuButton<int>となります。
onSelectedコールバックはそれぞれのアイテムが選択された時に呼び出され、引数には、選択されたアイテムに対応するvalueが渡されます。0番目を押すと、引数に0が渡されたonSelectedコールバックが呼び出される。
↓
listOfRoute[0](context)
が呼び出される。
↓
pushPage関数が呼び出されて、FirstPageに遷移する。
という感じです。とりあえず動かしてみるためにPopupMenuItem<int>としていますが、エラーを早く察知するために普通はint型ではなくenum(列挙型)を使うのが定石です。
状態セット(サンプル2)
//PopupMenuButtonのサンプル2(状態セット) import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { int count = 0; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( actions: [ PopupMenuButton<int>( itemBuilder: (context) { return const [ PopupMenuItem( value: 0, child: Text("0番目"), ), PopupMenuItem( value: 1, child: Text("1番目"), ), PopupMenuItem( value: 2, child: Text("2番目"), ), ]; }, onSelected: (value) { setState( () => count = value, ); }, ), ], ), body: Center( child: Text( "$count", style: const TextStyle( fontSize: 130.0, ), ), ), ), ); } }
状態変数を用意してポップアップメニューのアイテムを選択するとそのvalueが状態にセットされる例です。0番目を押すと0、1番目を押すと1、、、と画面に表示されます。
あとはcolor,icon,padding,elevationなど見た目を整えるためのパラメータや、tooltip,enabledなどのパラメータが用意されています。特に難しいものもないのでAPIドキュメントやソースコードを見ていただければと思います。
上記のサンプルはどちらもPopupMenuButtonの型引数をint型としています。例えばサンプル2で、2番目(3つ目)のアイテムのvalueを(本来2に設定すべきところを)間違って3にしてしまった場合、
PopupMenuItem( value: 3,//←間違い child: Text("2番目"), ),
状態countに3がセットされ、画面に3が表示されます。間違った値が使われているのにコンパイルエラー・実行時エラーが発生せず、間違った値がそのまま使われてしまう、ということですね。
プログラムが複雑になってくると、どこかで辻褄が合わなくなってエラーが出るかもしれませんが、その時どこが原因なのか、わかりづらいですね。
まあこんなシンプルな例なら、人間が見てすぐおかしいと気づくかもしれませんが、値を計算してその結果をどうこう、というふうになってくると気づきにくいケースもあるでしょう。
ですのでこういう場合列挙型(enumration,enum)を使いましょう。
//PopupMenuButtonのサンプル3(状態セット,enumバージョン) import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } enum CountState { zero, first, second, } Map<CountState, int> countMap = { CountState.zero: 0, CountState.first: 1, CountState.second: 2, }; class _MyAppState extends State<MyApp> { CountState count = CountState.zero; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( actions: [ PopupMenuButton<CountState>( itemBuilder: (context) { return const [ PopupMenuItem( value: CountState.zero, child: Text("0番目"), ), PopupMenuItem( value: CountState.first, child: Text("1番目"), ), PopupMenuItem( value: CountState.second, child: Text("2番目"), ), ]; }, onSelected: (value) { setState( () => count = value, ); }, ), ], ), body: Center( child: Text( "${countMap[count]}", style: const TextStyle( fontSize: 130.0, ), ), ), ), ); } }
動き自体はサンプル2と全く同じですが、例えば、2番目のアイテム(3つ目)のvalueに(例えばタイプミスなどで)間違った値を設定しようとしても、
上記のようにコンパイルエラーが出ますので、PopupMenuItemのvalueに間違った値を設定することは不可能です。(列挙型CountStateの三つの値以外をセットすることはできない)
したがって状態変数countに間違った値がセットされる可能性もありません。
列挙型について詳しくは、
↑こちらをご覧ください。