ymmooot

Flutter で Confirm Widget を関数として呼び出す

最近 Flutter を触っている。
フロントエンドでユーザーに確認のモーダルを出すときの自分が好きな実装パターンの一つである、
「モーダルを表示しユーザーに確認を問い、モーダルを閉じた後に結果を bool で返す非同期関数」の Flutter 版を書いた。

挙動は以下の通り。

1. モーダルを表示
2. ユーザーに確認を問う
3. モーダルを閉じる
  3.1. ユーザーが OK を押した場合: true を返す
  3.2. ユーザーが Cancel を押した場合: false を返す
  3.3. ユーザーが OK も Cancel も押さずにモーダルを閉じた場合: false を返す

お好きな Confirm Widget を作成する。とりあえず今回は以下のようなもの。

class SimpleConfirm extends StatelessWidget {
  const SimpleConfirm({
    Key? key,
    required this.title,
    required this.body,
    this.onOk,
    this.onCancel,
  }) : super(key: key);

  final String title;
  final String body;
  final VoidCallback? onOk;
  final VoidCallback? onCancel;

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text(title),
      content: Text(body),
      actions: [
        Padding(
            padding: const EdgeInsets.all(8),
            child: GestureDetector(
              child: const Text('Cancel'),
              onTap: () {
                onCancel?.call();
                Navigator.pop(context);
              },
            )),
        Padding(
            padding: const EdgeInsets.all(12),
            child: GestureDetector(
              child: const Text('OK'),
              onTap: () {
                onOk?.call();
                Navigator.pop(context);
              },
            )),
      ],
    );
  }
}

これを呼び出す関数は以下。

Future<bool> showConfirm(
  BuildContext context, {
  required String title,
  required String body,
}) async {
  final completer = Completer<bool>();
  await showDialog(
    context: context,
    builder: (context) => SimpleConfirm(
      title: title,
      body: body,
      onOk: () => completer.complete(true),
      onCancel: () => completer.complete(false),
    ),
  );
  if (!completer.isCompleted) {
    completer.complete(false);
  }
  return completer.future;
}

showDialog を await することで、Confirm Widget の外側をタップしたり端末の戻るボタンで Widget を閉じた場合にも対処できる。その場合は completer が未解決のままなので、complete(false) してあげれば良い。
こうすることで barrierDismissible: false にしたり、戻るボタンの挙動を停止したりする必要がないため、不必要に普段の UI から乖離させなくて済む。

以下のように呼び出すことができる。

final ok = await showConfirm(
  ctx, 
  title: "Are you sure?", 
  body: "Once you delete, you can't undo it.",
);

if (ok) {
  // call API
}