今回は、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を継承しています。
そのため、validator、onSaved、onChanged といったプロパティが最初から備わっています。
これにより、_formKey.currentState?.validate() を呼び出すだけで、フォーム全体のバリデーションをまとめて実行できるようになります。
この例では、1つしかFormがありませんが、新規ユーザー登録画面など入力項目が複数ある場合にvalidationなどの処理が一貫でき、大幅にDXを向上させます。
まとめ
なんで今までなかったんだという気持ちですが、ようやくFormFieldに追加されました。
FlutterのForm周りの処理はかなりサポートが多いので、則れるなら絶対にFormFieldのWidgetで作ることをオススメします。
ちょうど先日こんな感じのフォームを実装していたばっかりだったので、これに置き換えようと思います。