August 31, 2025

Flutter3.35から追加されたDropdownMenuFormFieldが便利!!

今回は、Flutter 3.35 で追加されたDropdownMenuFormField について解説します。 これによって、よくある入力フォームの実装がかなり楽になるのでおすすめです。

以前のDropdownButtonを使った複雑な実装と比較しながら、DropdownMenuFormFieldがどれほど便利になったかを見ていきましょう。

これまでのDropdownButton

Flutterでドロップダウンメニューを含むフォームを作成する場合、これまではStateful widgetや状態管理ツールを使って、DropdownButtonを使用するのが一般的でした。 さらに、DropdownButton自体がFormウィジェットと連携する機能を持っていなかったため、validatorやonSavedのような、フォームウィジェットが持つ便利な機能を利用できませんでした。 そのため、以下のようにFormウィジェットとGlobalKeyを使って、手動でバリデーションロジックを実装する必要がありました。

従来の書き方

この手のコードは何度も書いたことがあると思います。
ご存知の通りStateful widgetで値を保持し、ボタンを押したタイミングで、stateの値を愚直にチェックしています。

class OldDropdownFormExample extends StatefulWidget {
  const OldDropdownFormExample({super.key});

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

class _OldDropdownFormExampleState extends State<OldDropdownFormExample> {
  final _formKey = GlobalKey<FormState>();
  String? _selectedValue;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Old Dropdown Example")),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              DropdownButton<String>(
                value: _selectedValue,
                hint: const Text('Select an option'),
                items: ['Option 1', 'Option 2', 'Option 3']
                    .map(
                      (option) =>
                          DropdownMenuItem(value: option, child: Text(option)),
                    )
                    .toList(),
                onChanged: (value) {
                  setState(() {
                    _selectedValue = value;
                  });
                },
              ),
              const SizedBox(height: 24),
              ElevatedButton(
                onPressed: () {
                  // 手動でのバリデーションチェック
                  if (_selectedValue == null) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('選択してください')),
                    );
                  } else {
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('Success!')),
                    );
                  }
                },
                child: const Text('Validate'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

新しい書き方

今回追加された、DropdownMenuFormFieldを使って実装します。

class DropDownFormExample extends StatelessWidget {
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("DropDownFormField Example")),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              DropdownButtonFormField<String>(
                decoration: const InputDecoration(
                  labelText: 'Select an option',
                  border: OutlineInputBorder(),
                ),
                items: ['Option 1', 'Option 2', 'Option 3']
                    .map(
                      (option) =>
                          DropdownMenuItem(value: option, child: Text(option)),
                    )
                    .toList(),
                onChanged: (value) {
                  print('Selected: $value');
                },
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return '選択してください';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 24),
              ElevatedButton(
                onPressed: () {
                  if (_formKey.currentState?.validate() ?? false) {
                    ScaffoldMessenger.of(
                      context,
                    ).showSnackBar(const SnackBar(content: Text('Success!')));
                  }
                },
                child: const Text('Validate'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

DropdownMenuFormFieldはTextFormFieldやCheckboxListTileなどと同じく、FormFieldを継承しています。 そのため、validatoronSavedonChanged といったプロパティが最初から備わっています。 これにより、_formKey.currentState?.validate() を呼び出すだけで、フォーム全体のバリデーションをまとめて実行できるようになります。

この例では、1つしかFormがありませんが、新規ユーザー登録画面など入力項目が複数ある場合にvalidationなどの処理が一貫でき、大幅にDXを向上させます。

まとめ

なんで今までなかったんだという気持ちですが、ようやくFormFieldに追加されました。
FlutterのForm周りの処理はかなりサポートが多いので、則れるなら絶対にFormFieldのWidgetで作ることをオススメします。 ちょうど先日こんな感じのフォームを実装していたばっかりだったので、これに置き換えようと思います。

© AAkira 2023