【Flutter】アラート(ダイアログ)のカスタムウィジェット作ったので共有しておきます

#アラート(ダイアログ)のカスタムクラス作ったので共有しておきます
Flutterモバイルのダイアログになります。
iOSとAndroidでネイティブっぽいダイアログが出したかったので
色々探した上、今後自分自身よく使うだろうからカスタムウィジェット作りました。
良かったら使ってください。

フォントとかフォントサイズとかなるべく寄せてますが(主にiOS)
気に入らなければ適宜変更してください。

参考にさせていただいた記事
https://dev.classmethod.jp/articles/flutter_platform_ui1/

##Alertクラス
とりあえずクラス全文

```Dart:alert.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:google_fonts/google_fonts.dart';

class AlertButton {
  final String buttonTitle;
  final Function action;
  final bool isRed;
  AlertButton({this.action, this.buttonTitle = 'OK', this.isRed = false});
}

// ignore: must_be_immutable
class Alert extends StatefulWidget {
  final String title;
  final String message;
  List<AlertButton> buttons;
  Alert({@required this.title, @required this.message, this.buttons}) {
    if (buttons == null) buttons = [AlertButton(buttonTitle:'OK')];
  }
  @override
  _AlertState createState() => _AlertState();
}

class _AlertState extends State<Alert> {
  @override
  Widget build(BuildContext context) => _getAlert();

  Widget _getAlert() {
    if (Platform.isIOS) {
      return CupertinoAlertDialog(
        title: Text(
          widget.title,
          style: GoogleFonts.notoSans(fontSize: 16, height: 3, fontWeight: FontWeight.w900),
          textHeightBehavior: TextHeightBehavior(
            applyHeightToFirstAscent: false,
          ),
        ),
        content: Text(
          widget.message,
          style: GoogleFonts.notoSans(fontSize: 12, height: 1.25, fontWeight: FontWeight.w500),
        ),
        actions: _getActions(),
      );
    } else {
      return AlertDialog(
        title: Text(widget.title),
        content: Text(widget.message),
        actions: _getActions(),
      );
    }
  }

  List<Widget> _getActions() {
    List<Widget> _list = [];
    final buttons = widget.buttons.reversed.toList();
    if (Platform.isIOS) {
      for (int i = 0; i < buttons.length; i++) {
        _list.add(CupertinoDialogAction(
          child: Text(
              buttons[i].buttonTitle,
              style: GoogleFonts.notoSans(fontSize: 15, fontWeight: FontWeight.w500),
          ),
          isDestructiveAction: buttons[i].isRed, //赤いボタン
          onPressed: () => actionDismiss(buttons[i]),
        ));
      }
    } else {
      for (int i = 0; i < widget.buttons.length; i++) {
        _list.add(FlatButton(
          child: Text(buttons[i].buttonTitle),
          onPressed: () => actionDismiss(buttons[i]),
        ));
      }
    }
    return _list;
  }

  void actionDismiss(param) {
    if (param.action != null) param.action();
    Navigator.pop(context);
  }
}
```

フォントと寄せるためにGoogleFont入れています。
そのまま使う人はgoogle_fontsのパッケージ一応入れておいてください。

```yaml:pubspec.yaml
name: alert_app
description: A new Flutter application.

publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: ">=2.7.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.0
  google_fonts: ^1.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true
```

#使い方
パターン毎に使い方載せておきます。  
Alertのコンストラクタは`title:`、`message:`、`buttons`の3種類です。
`title:`、`message:`は必須です。
`buttons:`には`[AlertButton]`が入りますが
初期状態でOKのアラートボタンが入ってます。(アクション無し)
![スクリーンショット 2022-02-02 14.42.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385759/cc09868b-fade-f885-397a-e8bd453566d9.png)

###通常のアラート
- okボタンでダイアログが閉じるだけです。
- アラート以外の薄暗いところタップでも閉じます  
![alert.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385759/3829e7f6-6fc4-b48b-1629-5c209e536d91.gif)


```Dart:main.dart
showDialog(
  context: context,
  builder: (context) {
    return Alert(
      title: 'OKアラート',
      message: 'okボタンで閉じる',
    );
  },
)
```
###OKアクション
- okボタンで関数が走り、ダイアログを閉じます。
- アラート以外の薄暗いところタップでも閉じます  

Alertのコンストラクタ`buttons:`にAlertButtonのインスタンスを追加します。
AlertButtonのコンストラクタは`buttonTitle:`、`isRed:`、`action`の3種類です。
`buttonTitle:`の初期値は'OK'です。
`isRed:`の初期値は'false'です。　`isRed: true`でiOSのみ赤文字になります。
`action:`の初期値は'null'です。ボタン押下で走らせたい関数を入れます。

![okAction.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385759/97e566da-07f9-8c34-1df9-406594cbcfbf.gif)


```Dart:main.dart
showDialog(
  context: context,
  builder: (context) {
    return Alert(
      title: 'OKアクション',
      message: 'okボタンでaction:に表示',
      buttons: [
        AlertButton(
          action: () {
            setState(() {
              _result = 'OKアクション押下';
            });
          },
        )
      ],
    );
  },
)
```

###OKとキャンセルのアラート
- okボタンで関数が走り、ダイアログを閉じます。
- キャンセルボタンで関数が走り、ダイアログを閉じます。
- `action:`を設定しなければダイアログが閉じるだけになります。

```Dart:main.dart
showDialog(
  context: context,
  builder: (context) {
    return Alert(
      title: 'OK-Cancel',
      message: 'OK-Cancelボタンでaction:に表示',
      buttons: [
        AlertButton(action: () {
          setState(() {
            _result = 'OKアクション押下';
          });
        }),
        AlertButton(
          buttonTitle: 'Cancel',
          isRed: true,
          action: () {
            setState(() {
              _result = 'Cancelアクション押下';
            });
          },
        ),
      ],
    );
  },
)
```

###複数のアラートボタンも設定可能
- あんまり増やすとレイアウトエラー出ると思います。(SingleChildScrollViewとかで調整して下さい)

![スクリーンショット 2022-02-02 15.33.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385759/153ddbc0-135c-b896-ab29-2cc2d435b48d.png)


```Dart:main.dart
showDialog(
  context: context,
  builder: (context) {
    return Alert(
      title: '複数ボタンアラート',
      message: '複数のアラートボタンを追加できます',
      buttons: [
        AlertButton(action: () {
          setState(() {
            _result = 'OKアクション押下';
          });
        }),
        AlertButton(
            buttonTitle: 'Cancel',
            isRed: true,
            action: () {
              setState(() {
                _result = 'Cancelアクション押下';
              });
            }),
        AlertButton(action: () {
          setState(() {
            _result = 'OKアクション押下';
          });
        }),
        AlertButton(action: () {
          setState(() {
            _result = 'OKアクション押下';
          });
        }),
        AlertButton(action: () {
          setState(() {
            _result = 'OKアクション押下';
          });
        }),
        AlertButton(action: () {
          setState(() {
            _result = 'OKアクション押下';
          });
        }),
        AlertButton(action: () {
          setState(() {
            _result = 'OKアクション押下';
          });
        }),
      ],
    );
  },
);
```

###okボタン以外で閉じないように
- okボタンだけででダイアログが閉じます。
- アラート以外の薄暗いところタップでも閉じません
- `barrierDismissible: false`で出来ます。

カスタムクラス関係ありませんが、普通のshowDialogの使い方ですね。

```Dart:main.dart
showDialog(
  barrierDismissible: false,
  context: context,
  builder: (context) {
    return Alert(
      title: 'ok以外で閉じないアラート',
      message: '通常はアラート外タップでアラートが閉じますが、これはokボタン以外では閉じない用に設定できます',
    );
  },
)
```
###アラート外の薄暗いところタップでアクション設定する
- `Alert`を`WillPopScope`でwrapしてあげます。
- `onWillPop（）`に走らせたい関数を入れてあげます。
- `onWillPop（）`が戻り値必要なんで`Future.value(true)`を返してあげます。

`Future.value(true)`でダイアログ閉じる。
`Future.value(false)`だとダイアログ閉じなくなる。

![outside_action.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385759/b656fa93-c701-db60-9e6c-e60b029c6d67.gif)


```Dart:main.dart
showDialog(
  context: context,
  builder: (context) {
    return WillPopScope(
      onWillPop: () {
        setState(() {
          _result = 'アラート外アクション';
        });
        return Future.value(true);
      },
      child: Alert(
        title: 'アラート外アクション',
        message: '通常はアラート外タップでアラートが閉じますが、これはアラート外タップにアクションを設定できます',
        buttons: [
          AlertButton(
            action: () {
              setState(() {
                _result = 'アラート外OKアクション';
              });
            },
          )
        ],
      ),
    );
  },
)
```

#Example全文

```Dart:main.dart
import 'package:flutter/material.dart';

import 'alert.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Alert Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Alert Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String _result;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        children: [
          Container(
            padding: EdgeInsets.fromLTRB(16, 20, 16, 100),
            child: InkWell(
              splashColor: Colors.white,
              onTap: () {
                setState(() {
                  _result = null;
                });
              },
              child: Row(
                children: [
                  Text(
                    'action:',
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  ),
                  SizedBox(width: 16),
                  Text(
                    _result ?? 'null',
                    style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.red),
                  ),
                ],
              ),
            ),
          ),
          Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Container(
                  height: 40,
                  padding: EdgeInsets.zero,
                  child: ElevatedButton(
                    onPressed: () {
                      showDialog(
                        context: context,
                        builder: (context) {
                          return Alert(
                            title: 'OKアラート',
                            message: 'okボタンで閉じる',
                          );
                        },
                      );
                    },
                    child: Text(
                      "OKアラート",
                      style: TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.w600,
                        fontSize: 13.5,
                      ),
                    ),
                    style: ElevatedButton.styleFrom(
                      primary: Color.fromRGBO(0, 122, 255, 1),
                      shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))),
                    ),
                  ),
                ),
                SizedBox(height: 16),
                Container(
                  height: 40,
                  padding: EdgeInsets.zero,
                  child: ElevatedButton(
                    onPressed: () {
                      showDialog(
                        context: context,
                        builder: (context) {
                          return Alert(
                            title: 'OKアクション',
                            message: 'okボタンでaction:に表示',
                            buttons: [
                              AlertButton(
                                action: () {
                                  setState(() {
                                    _result = 'OKアクション押下';
                                  });
                                },
                              )
                            ],
                          );
                        },
                      );
                    },
                    child: Text(
                      'OKアクション',
                      style: TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.w600,
                        fontSize: 13.5,
                      ),
                    ),
                    style: ElevatedButton.styleFrom(
                      primary: Color.fromRGBO(0, 122, 255, 1),
                      shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))),
                    ),
                  ),
                ),
                SizedBox(height: 16),
                Container(
                  height: 40,
                  padding: EdgeInsets.zero,
                  child: ElevatedButton(
                    onPressed: () {
                      showDialog(
                        context: context,
                        builder: (context) {
                          return Alert(
                            title: 'OK-Cancel',
                            message: 'OK-Cancelボタンでaction:に表示',
                            buttons: [
                              AlertButton(action: () {
                                setState(() {
                                  _result = 'OKアクション押下';
                                });
                              }),
                              AlertButton(
                                buttonTitle: 'Cancel',
                                isRed: true,
                                action: () {
                                  setState(() {
                                    _result = 'Cancelアクション押下';
                                  });
                                },
                              ),
                            ],
                          );
                        },
                      );
                    },
                    child: Text(
                      'OK-Cancel',
                      style: TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.w600,
                        fontSize: 13.5,
                      ),
                    ),
                    style: ElevatedButton.styleFrom(
                      primary: Color.fromRGBO(0, 122, 255, 1),
                      shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))),
                    ),
                  ),
                ),
                SizedBox(height: 16),
                Container(
                  height: 40,
                  padding: EdgeInsets.zero,
                  child: ElevatedButton(
                    onPressed: () {
                      showDialog(
                        context: context,
                        builder: (context) {
                          return Alert(
                            title: '複数ボタンアラート',
                            message: '複数のアラートボタンを追加できます',
                            buttons: [
                              AlertButton(action: () {
                                setState(() {
                                  _result = 'OKアクション押下';
                                });
                              }),
                              AlertButton(
                                  buttonTitle: 'Cancel',
                                  isRed: true,
                                  action: () {
                                    setState(() {
                                      _result = 'Cancelアクション押下';
                                    });
                                  }),
                              AlertButton(action: () {
                                setState(() {
                                  _result = 'OKアクション押下';
                                });
                              }),
                              AlertButton(action: () {
                                setState(() {
                                  _result = 'OKアクション押下';
                                });
                              }),
                              AlertButton(action: () {
                                setState(() {
                                  _result = 'OKアクション押下';
                                });
                              }),
                              AlertButton(action: () {
                                setState(() {
                                  _result = 'OKアクション押下';
                                });
                              }),
                              AlertButton(action: () {
                                setState(() {
                                  _result = 'OKアクション押下';
                                });
                              }),
                            ],
                          );
                        },
                      );
                    },
                    child: Text(
                      '複数ボタンアラート',
                      style: TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.w600,
                        fontSize: 13.5,
                      ),
                    ),
                    style: ElevatedButton.styleFrom(
                      primary: Color.fromRGBO(0, 122, 255, 1),
                      shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))),
                    ),
                  ),
                ),
                SizedBox(height: 16),
                Container(
                  height: 40,
                  padding: EdgeInsets.zero,
                  child: ElevatedButton(
                    onPressed: () {
                      showDialog(
                        barrierDismissible: false,
                        context: context,
                        builder: (context) {
                          return Alert(
                            title: 'ok以外で閉じないアラート',
                            message: '通常はアラート外タップでアラートが閉じますが、これはokボタン以外では閉じない用に設定できます',
                          );
                        },
                      );
                    },
                    child: Text(
                      'ok以外で閉じないアラート',
                      style: TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.w600,
                        fontSize: 13.5,
                      ),
                    ),
                    style: ElevatedButton.styleFrom(
                      primary: Color.fromRGBO(0, 122, 255, 1),
                      shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))),
                    ),
                  ),
                ),
                SizedBox(height: 16),
                Container(
                  height: 40,
                  padding: EdgeInsets.zero,
                  child: ElevatedButton(
                    onPressed: () {
                      showDialog(
                        context: context,
                        builder: (context) {
                          return WillPopScope(
                            onWillPop: () {
                              setState(() {
                                _result = 'アラート外アクション';
                              });
                              return Future.value(true);
                            },
                            child: Alert(
                              title: 'アラート外アクション',
                              message: '通常はアラート外タップでアラートが閉じますが、これはアラート外タップにアクションを設定できます',
                              buttons: [
                                AlertButton(
                                  action: () {
                                    setState(() {
                                      _result = 'アラート外OKアクション';
                                    });
                                  },
                                )
                              ],
                            ),
                          );
                        },
                      );
                    },
                    child: Text(
                      'アラート外アクション',
                      style: TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.w600,
                        fontSize: 13.5,
                      ),
                    ),
                    style: ElevatedButton.styleFrom(
                      primary: Color.fromRGBO(0, 122, 255, 1),
                      shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
```