🕳️

【Flutter】実はshowDialog()には返り値があるって知ってた?

に公開

はじめに

株式会社Sally 所属エンジニアの @wellPicker です。
弊社では、スマホやパソコンでマダミスを遊べるアプリであるウズや、マダミス情報・予約管理サイトマダミス.jp、マダミス開発ツールウズスタジオを開発しています。
マダミス自体についてはこちらをご覧ください。

今回はタイトルの通り、特に初学者の方向けの記事となっています。
なので、「知ってるよ。当たり前じゃないか」という方にとってはあまり目新しい情報はないかもしれません。その場合はぜひ「いいね」を押してから閉じていただければと思います(笑)。

一方で、「showDialog()はダイアログを出すだけの関数でしょ? 返り値ってあるの?」と思った方もいるかもしれません。確かに「とりあえずダイアログを表示するだけ」であれば返り値は不要なので、意外と知られていないように思います。
そこで今回は、showDialog()の返り値に関して説明したいと思います。

まずはshowDialog()の定義を見てみる

まずは公式の定義を見てみましょう。

Future<T?> showDialog<T>({
  required BuildContext context,
  required WidgetBuilder builder,
  bool barrierDismissible = true,
  Color? barrierColor,
  String? barrierLabel,
  bool useSafeArea = true,
  bool useRootNavigator = true,
  RouteSettings? routeSettings,
  Offset? anchorPoint,
  TraversalEdgeBehavior? traversalEdgeBehavior,
  bool fullscreenDialog = false,
  bool? requestFocus,
  AnimationStyle? animationStyle,
}) {
// 内部処理
}

大事なのは、showDialog()はFuture<T?>を返す関数であることです。
つまり、非同期処理で任意の型の値を返すことが可能です。
T型について詳しくない方は公式ドキュメント、または他の記事などを読んで調べてみてください。

非同期処理といえば、通常は時間がかかる処理の結果を待つのに使用するものです。では、showDialog()は一体何を待っているのでしょうか?
基本的には、ユーザーの反応を待つために使用します。

ユーザーの反応を待つダイアログの例

「ダイアログを表示する」と言っても、目的は色々あると思います。
しかし、多くの場合においては、次のように「ユーザーに何かしら確認・反応して欲しい」時に使用するのではないでしょうか。

例えばこの例において、ユーザーが「エントリーを確定」したのか、「キャンセル」したのか。それを知るために、showDialog()の返り値を使用することが可能です。

...
      final result = await showDialog<bool>(
        context: context,
        builder: (_context) => AlertDialog(
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(true),
              child: Text('エントリーを確定'),
            ),
            TextButton(
              onPressed: () => Navigator.of(context).pop(false),
              child: Text('キャンセル'),
            ),
          ],
        ),
      );
      if (result != null && result) {
        // エントリー処理
      } else {
        // キャンセル処理
      }
...

上記のように、Navigator.of(context).pop() を使用して、ダイアログを閉じつつ showDialog() の返り値を指定することが可能です。
ここで帰ってきたbool値を見て、次にどういう処理をするか分岐させることができます。

showDialog()の返り値を使用するメリット

「ダイアログ内に処理を書けば済むのでは?」と思うかもしれません。
確かに小規模な処理ならそれで十分です。しかし、処理が複雑になると状況は変わります。

例えば先ほどの例で「エントリーを確定」したらそのイベントにエントリーするわけですが、このエントリー処理が非常に複雑な関数だったらどうでしょうか。

...
      await showDialog<bool>(
        context: context,
        builder: (_context) => AlertDialog(
          actions: [
            TextButton(
              onPressed: () {
                // 複雑なエントリー処理
                manyPropertyMethod(
                  property1: property1,
                  property2: property2,
                  property3: property3,
                  property4: property4,
                  property5: property5,
                  property6: property6,
                  property7: property7,
                  property8: property8,
                  property9: property9,
                  property10: property10,
                );
              },
              child: Text('エントリーを確定'),
            ),
            TextButton(
              onPressed: () => Navigator.of(context).pop(false),
              child: Text('キャンセル'),
            ),
          ],
        ),
      );
...

ネストが深いところにこれだけ複雑な関数を書くと、結構読みづらそうですね。
もしもmanyPropertyMethod()の引数にさらにコールバック関数やビルダーがあったら……? 可読性が大きく下がり、バグが入る可能性が高まります。実はこの記事を書いたのも、複雑にネストした実装が原因でバグが発生したことがきっかけです。

先ほどの実装を、showDialog()の返り値を利用して書き直すと次のようになります。

...
      final result = await showDialog<bool>(
        context: context,
        builder: (_context) => AlertDialog(
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(true),
              child: Text('エントリーを確定'),
            ),
            TextButton(
              onPressed: () => Navigator.of(context).pop(false),
              child: Text('キャンセル'),
            ),
          ],
        ),
      );
      if (result != null && result) {
        // 複雑なエントリー処理
        manyPropertyMethod(
          property1: property1,
          property2: property2,
          property3: property3,
          property4: property4,
          property5: property5,
          property6: property6,
          property7: property7,
          property8: property8,
          property9: property9,
          property10: property10,
        );
      } else {
        // キャンセル処理
      }
...

処理の本体をダイアログの外に切り出せるため、ネストが浅くなり全体の流れが理解しやすくなっていることがわかります。
こちらの方が実装の流れもわかりやすく、読みやすいと感じられるのではないでしょうか。

また、ダイアログの領域外をタップして閉じた場合showDialog()はnullを返します
showDialogの外部に次の処理を出すことで、ダイアログの領域外をタップして閉じた場合も検出して処理を実行することができるのはメリットとなります。

まとめ

  • showDialog() は Future<T?> の関数
  • Navigator.pop(value) で返り値を渡せる
  • 複雑な処理をダイアログ外に出せるので 可読性・保守性が向上
  • ダイアログの領域外をタップして閉じた時の処理も書ける

今回は、showDialog()の返り値、およびその活用法について説明しました。
メリットの項でも触れた通り、showDialog()の返り値は必ずしも明確なメリットがあるものではありません。showDialog()のbuilder内に直接処理を書いてしまえば同じ実装はできるのです。
しかし、どうせ同じ実装を書くならばより可読性が高い方が良いです。特に昨今はAIコーディングが主流となってきているため、「AIが誤読しない」「可読性が低いコードをAIに書かせない」ためにもこれまで以上に実装の可読性を高めることが重要になっていると感じます。
そのために、こうした書き方があるということを頭の片隅に留めておいていただければ幸いです。

UZU テックブログ

Discussion