対象者
- Flutterでフォームのバリデーションをカスタマイズしたい開発者
CheckBox
や他のウィジェットをフォーム内で検証したい人- Flutterのフォームフィールドの挙動を深く理解したいエンジニア
はじめに
Flutterでフォームを作成する際、TextFormField
を使用すればテキスト入力のバリデーションは簡単に行えます。しかし、CheckBox
やDropdownButton
などの他のウィジェットをフォーム内でバリデーションする方法は少し複雑です。本記事では、3つのチェックボックスを例に取り、それぞれ異なるバリデーション方法を実装し、その違いと実装方法を詳しく解説します。また、FormField
の標準的な使い方や主要なプロパティについても説明します。これにより、フォーム内で多様なウィジェットを扱う際の参考になります。
バリデーションの実行
フォームのバリデーションは、ユーザーが入力を完了した後に検証ボタンを押すことで行われます。以下のコードは、検証ボタンの処理を示しています。この部分は3つのチェックボックスに共通しています。
// 検証ボタンの処理
FilledButton(
onPressed: () {
final result =
_formKey.currentState!.validate() ? '検証成功!' : '検証失敗!';
setState(() {
_isValidated = true;
_label = result;
});
},
child: Text(_label),
),
解説
-
_formKey:
GlobalKey<FormState>
のインスタンスで、フォーム全体の状態を管理します。 -
_formKey.currentState!.validate(): フォーム内のすべての
FormField
のvalidator
を呼び出し、バリデーションを実行します。すべてのフィールドが有効であればtrue
を返します。 -
_isValidated フラグ: バリデーションが実行されたことを示すフラグで、3つめのチェックボックスでのエラーメッセージの表示制御に使用します。
-
_label: ボタンのラベルを更新し、バリデーションの結果(「検証成功!」または「検証失敗!」)をユーザーに表示します。
このボタンを押すことで、フォーム全体のバリデーションが実行され、各フィールドのvalidator
が評価されます。バリデーションの結果に応じて、エラーメッセージが表示されます。
FormFieldの基本的な使い方
FormField
は、フォーム内で任意のウィジェットを扱い、バリデーションを適用するためのウィジェットです。FormField<T>
のようにジェネリクスを使用して、扱う値の型を指定します。今回はチェックボックスを扱うため、FormField<bool>
を使用しています。
主なプロパティとメソッド
-
builder: フォームフィールドのUIを構築するための関数を指定します。
FormFieldState<T>
を引数に取り、ウィジェットを返します。 -
validator: フィールドの値を検証するための関数を指定します。無効な場合はエラーメッセージを返し、有効な場合は
null
を返します。 -
autovalidateMode: 自動バリデーションのタイミングを制御します。以下の値が使用できます:
AutovalidateMode.disabled
: 自動バリデーションを行いません。AutovalidateMode.always
: 常にバリデーションを行います。AutovalidateMode.onUserInteraction
: ユーザーの操作時にバリデーションを行います。
-
state.didChange(value): フォームフィールドの状態が変更されたことを通知します。これを呼び出すことで、
validator
が再評価され、UIが更新されます。
チェックボックスの場合の注意点
Checkbox
ウィジェット自体にはバリデーション機能がないため、FormField<bool>
を使用してバリデーションを実装します。value
の型がbool
であることを明示することで、適切なバリデーションと状態管理が可能になります。
チェックボックスのバリデーション方法の比較
本例では、3つのチェックボックスを使用して異なるバリデーション方法を実装しています。それぞれの違いとソースコードを以下に示します。
1. AutovalidateMode.onUserInteraction
を使用する方法
多分、標準的な使用方法。
この方法では、ユーザーの操作に応じてバリデーションを行います。state.didChange(value)
を呼び出すことで、フォームの状態を更新します。
FormField<bool>(
autovalidateMode: AutovalidateMode.onUserInteraction,
builder: (state) {
return Row(
children: <Widget>[
Checkbox(
value: _checkboxValueFormal,
onChanged: (value) {
if (value == null) {
return;
}
setState(() {
_checkboxValueFormal = value;
state.didChange(value);
});
},
),
const Text('onUserInteractionを利用'),
Text(
state.errorText ?? '',
),
],
);
},
validator: (value) {
if (value == true) {
return null;
}
return '(ユーザの入力で変化)';
},
),
ポイント:
-
ジェネリクスの型指定:
FormField<bool>
として、bool
型の値を扱うことを明示しています。 -
autovalidateMode
の設定:AutovalidateMode.onUserInteraction
を設定することで、ユーザーが操作したときに自動でバリデーションが行われます。 -
state.didChange(value)
の呼び出し:onChanged
内でstate.didChange(value)
を呼び出し、フォームフィールドの状態を更新し、バリデーションを再評価します。 -
エラーメッセージの表示:
state.errorText
を使用して、バリデーション結果に応じたエラーメッセージを表示します。
2. メンバー変数を使用して自前でデータを管理する方法
この方法では、state.didChange(value)
を使用せず、メンバー変数を直接操作してバリデーションを行います。
FormField<bool>(
builder: (state) {
return Row(
children: <Widget>[
Checkbox(
value: _checkboxValuePrivateValue,
onChanged: (value) {
if (value == null) {
return;
}
setState(() {
_checkboxValuePrivateValue = value;
});
},
),
const Text('メンバー変数を使用'),
Text(state.errorText ?? ''),
],
);
},
validator: (_) {
if (_checkboxValuePrivateValue == true) {
return null;
}
return '(検証時のみ動作)';
},
),
ポイント:
-
ジェネリクスの型指定: こちらも
FormField<bool>
を使用しています。 -
state.didChange
を使用しない:state.didChange(value)
を呼び出さず、メンバー変数_checkboxValuePrivateValue
を直接更新します。 -
バリデーションのタイミング:
autovalidateMode
を指定していないため、検証ボタンを押したとき(_formKey.currentState!.validate()
が呼ばれたとき)のみバリデーションが行われます。
3. 検証直後のみエラーメッセージを表示する方法
この方法では、バリデーションフラグ_isJustAfterVerification
を使用し、検証ボタンを押した直後のみエラーメッセージが表示されるように制御します。
// フラグの初期化
var _checkboxValueOnlyAfterValidation = false;
var _isJustAfterVerification = false;
FormField<bool>(
autovalidateMode: AutovalidateMode.onUserInteraction,
builder: (state) {
return Row(
children: <Widget>[
Checkbox(
value: _checkboxValueOnlyAfterValidation,
onChanged: (value) {
if (value == null) {
return;
}
setState(() {
_checkboxValueOnlyAfterValidation = value;
state.didChange(value);
});
_isJustAfterVerification = false;
},
),
const Text('検証直後のみエラーを出す'),
Text(
state.errorText ?? '',
),
],
);
},
validator: (value) {
if (value == true || !_isJustAfterVerification) {
return null;
}
return '(検証後)';
},
),
FilledButton(
onPressed: () {
_isJustAfterVerification = true;
final result =
_formKey.currentState!.validate() ? '検証成功!' : '検証失敗!';
setState(() {
_label = result;
});
},
child: Text(_label),
),
ポイント:
-
フラグによる制御:
_isJustAfterVerification
フラグを使用して、バリデーションの結果を管理します。 -
autovalidateMode
の設定:AutovalidateMode.onUserInteraction
を設定していますが、フラグによってエラーメッセージの表示を制御します。 -
state.didChange
の呼び出し:state.didChange(value)
を呼び出して、フォームフィールドの状態を更新します。 -
エラーメッセージの表示制御:
validator
内で_isJustAfterVerification
フラグをチェックし、必要な場合のみエラーメッセージを返します。
動作の流れ
以下は、チェックボックスのバリデーションが「検証直後のみエラーを出す」動作としてどのように機能しているかをわかりやすくまとめたものです。
-
検証ボタンの押下
- ユーザーが検証ボタンを押す
_isJustAfterVerification
フラグをtrue
に設定- フォーム全体のバリデーションを実行 (
_formKey.currentState!.validate()
)- 各
FormField
のvalidator
が呼び出される
- 各
-
バリデーション処理
- 各
validator
内で以下の条件をチェック- チェックボックスがチェックされている (
value == true
) または _isJustAfterVerification
がfalse
の場合- エラーメッセージは表示されない (
null
を返す)
- エラーメッセージは表示されない (
- それ以外の場合
- エラーメッセージを返す (
'(検証後)'
)
- エラーメッセージを返す (
- チェックボックスがチェックされている (
- 各
-
エラーメッセージの表示
- 検証ボタン押下後、チェックボックスが未チェックの場合にエラーメッセージが表示される
- チェックボックスがチェックされている場合はエラーなし
-
チェックボックスの変更時
- ユーザーがチェックボックスを変更する (
onChanged
が呼び出される)- チェックボックスの値を更新 (
_checkboxValueOnlyAfterValidation
) _isJustAfterVerification
フラグをfalse
にリセットstate.didChange(value)
を呼び出してフォームフィールドの状態を更新- これにより、ユーザーの操作に基づいてバリデーションが再評価される
- チェックボックスの値を更新 (
- ユーザーがチェックボックスを変更する (
-
エラーメッセージの非表示
- チェックボックスを変更して
_isJustAfterVerification
がfalse
に設定されると、- 再度バリデーションを実行してもエラーメッセージは表示されない
- エラーメッセージの表示は検証ボタン押下直後のみに限定される
- チェックボックスを変更して
このようにして、ユーザーが検証ボタンを押した直後のみエラーメッセージが表示され、その後のチェックボックスの変更ではエラーメッセージが表示されないように制御しています。これにより、ユーザーエクスペリエンスを向上させ、不要なエラーメッセージの表示を防ぐことができます。
Q&A
Q1: state.didChange(value)
を呼び出すタイミングはいつが適切ですか?
A1: ユーザーがウィジェットを操作して値が変化したときに、state.didChange(value)
を呼び出すことで、フォームの状態とバリデーションを更新できます。特に、autovalidateMode
が設定されている場合は、これを呼び出すことで即時にバリデーション結果が反映されます。
Q2: バリデーションフラグ_isValidated
を使うメリットは何ですか?
A2: _isValidated
フラグを使うことで、ユーザーがチェックを外した際に不要なエラーメッセージが表示されるのを防ぎ、検証ボタンを押した直後のみエラーを表示することができます。これにより、ユーザーエクスペリエンスを向上させることができます。
Q3: autovalidateMode
の違いは何ですか?
A3: autovalidateMode
はバリデーションの自動実行タイミングを制御します。
-
AutovalidateMode.disabled
: 自動バリデーションを行わず、Form
のvalidate()
メソッドを呼び出したときのみバリデーションが実行されます。 -
AutovalidateMode.always
: 常にバリデーションを行い、フィールドの値が変化するたびにバリデーションが実行されます。 -
AutovalidateMode.onUserInteraction
: ユーザーがフィールドを操作したときにバリデーションを行います。
まとめ
チェックボックスをフォーム内でバリデーションする3つの方法を比較し、それぞれの特徴と実装方法を解説しました。FormField
を使用することで、チェックボックスや他のウィジェットでもバリデーションを適用できます。特に、FormField<T>
のジェネリクスを活用し、state.didChange
やvalidator
、autovalidateMode
を適切に組み合わせることで、柔軟なバリデーションが可能です。また、共通の検証ボタンを使用してフォーム全体のバリデーションを実行する方法も説明しました。用途に応じて最適な方法を選択し、ユーザーにとって使いやすいフォームを作成しましょう。
ソース(main.dartにコピペして動作確認用)
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _formKey = GlobalKey<FormState>();
var _label = '検証';
var _checkboxValueFormal = false;
var _checkboxValuePrivateValue = false;
var _checkboxValueOnlyAfterValidation = false;
var _isJustAfterVerification = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FormField<bool>(
autovalidateMode:
AutovalidateMode.onUserInteraction, // state.didChangeと連動
builder: (state) {
return Row(
children: <Widget>[
Checkbox(
value: _checkboxValueFormal,
onChanged: (value) {
if (value == null) {
return;
}
setState(() {
_checkboxValueFormal = value;
state.didChange(value);
});
}),
const Text('onUserInteractionを利用'),
Text(
state.errorText ?? '',
)
],
);
},
validator: (value) {
if (value == true) {
return null;
}
return '(ユーザの入力で変化)';
},
),
FormField<bool>(
builder: (state) {
return Row(
children: <Widget>[
Checkbox(
value: _checkboxValuePrivateValue,
onChanged: (value) {
if (value == null) {
return;
}
setState(() {
_checkboxValuePrivateValue = value;
});
}),
const Text('メンバー変数を使用'),
Text(state.errorText ?? '')
],
);
},
validator: (_) {
if (_checkboxValuePrivateValue == true) {
return null;
}
return '(検証時のみ動作)';
},
),
FormField<bool>(
autovalidateMode: AutovalidateMode.onUserInteraction,
builder: (state) {
return Row(
children: <Widget>[
Checkbox(
value: _checkboxValueOnlyAfterValidation,
onChanged: (value) {
if (value == null) {
return;
}
setState(() {
_checkboxValueOnlyAfterValidation = value;
state.didChange(value);
});
_isJustAfterVerification = false;
}),
const Text('検証直後のみエラーを出す'),
Text(
state.errorText ?? '',
)
],
);
},
validator: (value) {
if (value == true || !_isJustAfterVerification) {
return null;
}
return '(検証後)';
},
),
FilledButton(
onPressed: () {
_isJustAfterVerification = true;
final result =
_formKey.currentState!.validate() ? '検証成功!' : '検証失敗!';
setState(() {
_label = result;
});
},
child: Text(_label)),
],
),
),
);
}
}