2021/5/21 : Flutter : PopupMenuButtonと列挙型(enum)を使ってみよう。

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に間違った値がセットされる可能性もありません。

列挙型について詳しくは、

2021/1/8 A tour of the Dart language>>Enumerated typesの訳

↑こちらをご覧ください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です