ダイアログ内で入力する – Flutter

[PR]

入力フォーム系、3つめ。たぶんラスト。
ダイアログの中に入力フォームをセット。

とりあえずコード

import 'package:flutter/material.dart';

// 必須チェック
FormFieldValidator _requiredValidator(BuildContext context) => (val) => val.isEmpty ? "必須" : null;

class FormDialogPage extends StatefulWidget {
  @override
  State createState() => _FormDialogPageState();
}

class _FormDialogPageState extends State {
  final GlobalKey _formKey = GlobalKey();

  // 挙動確認のため、setState()対象
  var _enteredValue1 = "上のテキスト";
  var _enteredValue2 = "下のテキスト";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('📮📮📮')),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: ListView(
          children: [
            TextFormFieldInputDialog(
                context: context,
                key: _formKey,
                label: "TextFormFieldっぽい見た目",
                value: _enteredValue1,
                validator: _requiredValidator(context),
                maxLength: 40,
                isMultipleLines: false,
                submitAction: (String inputText) {
                  // TODO inputTextを処理する
                  setState(() => _enteredValue1 = inputText);
                }),
            SizedBox(height: 32.0),
            OutlineButton(
              child: Text("「$_enteredValue2」を編集"),
              onPressed: showInputDialog(
                  context: context,
                  key: _formKey,
                  label: "普通のボタンから呼び出し",
                  value: _enteredValue2,
                  validator: _requiredValidator(context),
                  maxLength: 160,
                  isMultipleLines: true,
                  submitAction: (String inputText) {
                    // TODO inputTextを処理する
                    setState(() => _enteredValue2 = inputText);
                  }),
            ),
          ],
        ),
      ),
    );
  }
}

// ダイアログで入力する場合
class TextFormFieldInputDialog extends InkResponse {
  TextFormFieldInputDialog({
    @required BuildContext context,
    @required GlobalKey key, // 同じ画面のこのダイアログ以外で同じキーを使ってるとエラーになるので注意
    @required String label,
    @required String value,
    FormFieldValidator validator,
    @required int maxLength,
    bool isMultipleLines = false,
    @required Function submitAction,
  }) : super(
          onTap: showInputDialog(
            context: context,
            key: key,
            label: label,
            value: value,
            validator: validator,
            maxLength: maxLength,
            submitAction: submitAction,
          ),
          child: InputDecorator(
            decoration: InputDecoration(border: OutlineInputBorder(), labelText: label),
            child: Text(value),
          ),
        );
}

// showDialog()
GestureTapCallback showInputDialog({
  @required BuildContext context,
  @required GlobalKey key,
  @required String label,
  @required String value,
  FormFieldValidator validator,
  @required int maxLength,
  bool isMultipleLines = false,
  @required Function submitAction,
}) {
  return () {
    // TextEditingControllerはダイアログを起動してからの処理内で完結させても問題なさそう
    TextEditingController textEditingController = TextEditingController(text: value);
    showDialog(
        context: context,
        builder: (BuildContext context) {
          return AlertDialog(
            content: Form(
              key: key,
              child: TextFormField(
                decoration: InputDecoration(border: OutlineInputBorder(), labelText: label),
                controller: textEditingController,
                validator: validator,
                maxLength: maxLength,
                autofocus: true,
                onFieldSubmitted: (_) {
                  // 1行の場合はキーボードのエンターキーでもOKタップと同じ挙動にする
                  if (key.currentState.validate()) {
                    Navigator.pop(context, true); // バリデートOK→ダイアログを閉じてから処理する
                  }
                },

                // 複数行対応
                keyboardType: (isMultipleLines) ? TextInputType.multiline : TextInputType.text,
                maxLines: (isMultipleLines) ? 3 : 1,
              ),
            ),
            actions: [
              FlatButton(
                child: const Text('キャンセル'),
                textTheme: ButtonTextTheme.normal,
                onPressed: () => Navigator.of(context).pop(null),
              ),
              FlatButton(
                child: const Text('OK'),
                textTheme: ButtonTextTheme.normal,
                onPressed: () {
                  if (key.currentState.validate()) {
                    Navigator.pop(context, true); // バリデートOK→ダイアログを閉じてから処理する
                  }
                  return;
                },
              ),
            ],
          );
        }).then((isConfirm) {
      if (isConfirm != null) {
        // OKを選択してバリデートを通った場合のみ、ローカルのTextEditingControllerから入力値を取得して処理を実行
        submitAction(textEditingController.value.text);
      }
    });
  };
}

 

InputDecoratorウィジェット
decorationでInputDecoration()をセットできる = 普通のTextウィジェットでも入力欄っぽく見せることができる。

AlertDialogのcontent
・TextFormFieldだけ配置して、複数行の場合でも最大行数を抑えめに
・そうしないとレイアウトで不具合出がち
・titleも場所をとるので今回は除外

AlertDialogのactions
・OKをタップしたときだけ処理ができればいいので、キャンセルボタンも返り値はnull
・OKタップ、もしくはキーボードのエンターキー(onFieldSubmitted)でバリデートを実行
問題なければダイアログを閉じてtrueを返す
・thenの中で処理
SetState処理を渡してるけど、ReduxだとActionを実行するとか
通信処理もダイアログを閉じてからローディングを表示して、必要があれば再度ダイアログを開けばいいと思う。

あんまり使いやすいわけではないけど、簡単にデータ登録とか編集させたくないけど、大げさにもしたくもないっていうときに使ってます。