flutter-building-forms について
Flutter 開発者向けに、Form のフィールド、バリデーション、フォーカス、キーボード、自動入力の実装方針を定めるスキルです。既存のテーマや構成を活用し、送信やエラー処理、検証方法も扱います。
主な機能
- 01Form 対応フィールドの選択
- 02検証とサーバーエラーの処理
- 03キーボード・フォーカス・自動入力の設定
- 04テーマへの準拠とエラー領域の確保
- 05フォーム固有の検証手順
活用シーン
- 01ログインまたは登録フォームの構築
- 02ドロップダウン選択の検証
- 03サーバー側フィールドエラーの表示
- 04キーボードとフォーカス動作の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ thesimpleapp/ flutter-app-skills --skill flutter-building-formsインストール後、Claude Code で/flutter-building-formsを入力して読み込まれたか確認します。
同梱ファイルreferences/recipes.md
詳細
Flutter でフォームを作成する
Flutter 3.47 以降では、Form / FormField を使ってフォームを実装します。GetX、flutter_form_builder、Expo/RN のフォームパターンは追加しないでください。このリポジトリに既存の Form ラッパー、共通バリデーター、ThemeData.inputDecorationTheme、または認証フォームを囲む AutofillGroup がある場合は、それらを拡張してください。ウィジェットが既存の ThemeExtension や *Pressable を読み取っている場合は、そのシステムに合わせてフィールドをスタイル設定してください。ブランドに合わせたサーフェスに、標準の Material デコレーションテーマを適用しないでください。
フォームは、プロジェクトで既に使われている ListView / CustomScrollView でスクロールさせます。キーボードの下に Column だけを置くとオーバーフローします。
参考資料
references/
recipes.md Login/signup autofill, server errors, custom FormField, lazy lists
必須ルール
Stateがfinal GlobalKey<FormState> _formKeyを所有します。build内でキー、FocusNode、TextEditingControllerを生成しないでください。送信処理では、if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); … }を使います。送信に失敗した場合は、validateGranularly()を実行してから、視覚的な順序で最初の無効なフィールドに対してFocusNode.requestFocus()を呼び出します(Setは順序付けされていません)。Form内では、検証または保存が必要な値はすべてTextFormField、DropdownMenuFormField、またはカスタムのFormField<T>にします。通常のTextFieldは使わないでください。validatorの型はFormFieldValidator<T>?で、String?を返します(nullは有効を意味します)。処理は軽量かつ同期的に保ちます。API / サーバーのエラーにはforceErrorTextを使います。autovalidateModeの既定値はAutovalidateMode.disabledです。最初の送信後はonUserInteractionまたはonUnfocusを推奨します。必須かつ空のフィールドにalwaysを使わないでください。AutovalidateMode.onFocusChangeはありません。onUserInteractionIfErrorも使用できます。ThemeData.inputDecorationTheme(InputDecorationThemeData)でスタイルを設定します。フィールドごとのInputDecorationではlabelText/hintText/helperText/prefixIconのみを設定します。- すべてのフィールドに
keyboardTypeとtextInputActionを設定します(最後のフィールドまではnext、最後はdone/go/sendとonFieldSubmittedを使います)。 autofillHints: const [AutofillHints.…]を設定します。ログインではusernameとpassword、サインアップではnewUsernameとnewPasswordを使います。ほかにemail、oneTimeCodeもあります。フィールド群をAutofillGroupで囲みます。送信に成功したら、TextInput.finishAutofillContext(shouldSave: true)を 1 回呼び出します。複数ステップのフォームでは、最後のステップの後に呼び出します。- コントローラーと
FocusNodeはState上で一度だけ作成し、dispose()で破棄します。遅延読み込みのListView/CustomScrollViewでは、コントローラーをステートフルな祖先が所有する必要があります。 errorTextの表示でフォームが再配置されないよう、ヘルパー / エラー領域を確保します(helperText: ' 'または高さを固定した親を使います)。Form内の新しいドロップダウンにはDropdownMenuFormFieldを使います。DropdownButtonFormFieldを引き続き使う場合は、非推奨のvalueではなくinitialValueを渡します。- 最初のフィールドに
autofocusを設定します。フォームをFocusTraversalGroupで囲みます。視覚的な順序と読み上げ順序が異なる場合に限り、OrderedTraversalPolicyとNumericFocusOrderを使います。 - 操作可能なフィールドには
const ValueKey<String>('kebab-name')を指定します。
Form の所有
class _SignInState extends State<SignInPage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late final FocusNode _emailFocus = FocusNode();
late final TextEditingController _email = TextEditingController();
AutovalidateMode _auto = AutovalidateMode.disabled;
@override
void dispose() {
_emailFocus.dispose();
_email.dispose();
super.dispose();
}
void _submit() {
final Set<FormFieldState<Object?>> invalid =
_formKey.currentState!.validateGranularly();
if (invalid.isNotEmpty) {
setState(() => _auto = AutovalidateMode.onUserInteraction);
_emailFocus.requestFocus(); // first invalid field in visual order
return;
}
_formKey.currentState!.save();
TextInput.finishAutofillContext(shouldSave: true);
}
}
ネストされたウィジェットでは、キーを引き回す代わりに Form.of(context)(Form がない場合はアサート)または Form.maybeOf を呼び出せます。フォームの検証。
validate() は子孫の validator をすべて実行し、エラーを表示するために再ビルドして、いずれかが失敗した場合は false を返します。save() は onSaved のみを呼び出し、検証はしません。onSaved を使う場合は、validate() が成功した後に save() を呼び出します。reset() は各フィールドを FormField.initialValue に戻します。validateGranularly() は、無効な FormFieldState の Set を返します。FormState.validate。
フィールド
TextFormField は TextField をラップする FormField<String> です。Form の祖先ウィジェットは省略できます。使用しない場合は、GlobalKey<FormFieldState> を渡し、その状態に対して validate/save/reset を呼び出します。エディターを FormState に参加させない場合に限り、TextField を使用してください。TextEditingController を渡すと、その .text がフィールドの initialValue になります。FormField.initialValue は最初のビルド時と reset() の呼び出し時に適用されます。TextFormField。
validate/save/reset が必要なドロップダウン:
DropdownMenuFormField<String>(
key: const ValueKey<String>('country-field'),
dropdownMenuEntries: const <DropdownMenuEntry<String>>[
DropdownMenuEntry<String>(value: 'us', label: 'United States'),
],
validator: (String? v) => v == null ? 'Pick a country' : null,
onSaved: (String? v) { /* store */ },
)
initialSelection を使って選択値を設定します(FormField.initialValue に対応します)。そのままの DropdownMenu は FormField ではありません。DropdownMenuFormField。
カスタムのチェックボックスグループ、スライダー、チップ選択には、builder で FormFieldState を参照し、state.didChange(newValue) を呼び出す FormField<T> を使用します。FormField。
バリデーション
TextFormField(
validator: (String? v) {
if (v == null || v.isEmpty) return 'Required';
return null;
},
forceErrorText: serverEmailError, // null resumes validator
autovalidateMode: AutovalidateMode.onUserInteractionIfError,
decoration: const InputDecoration(
labelText: 'Email',
helperText: ' ',
),
)
forceErrorText は validator を実行せずに FormFieldState.errorText を設定します。値が null 以外の間は validator は呼び出されず、InputDecoration.errorText よりも優先されます。ローカルバリデーションを再開するには、値をクリアしてください。forceErrorText。
削除された autovalidate の bool 値ではなく、autovalidateMode を使用してください。AutovalidateMode:
| 値 | 実行タイミング |
|---|---|
disabled | validate/validateGranularly のみ(Form/FormField のデフォルト) |
always | 最初のビルド時と変更のたび(必須項目が空だと描画時にエラーが表示されます) |
onUserInteraction | フィールドの内容が変更された後 |
onUnfocus | フィールドがフォーカスを失った後 |
onUserInteractionIfError | フィールドにすでにエラーがある場合に限り、変更のたび |
errorText が null 以外の場合、ヘルパーは非表示になり、フィールドの高さが変わることがあります。スペースを確保するには、helperText: ' ' または高さ固定の親ウィジェットを使用してください。
キーボード、アクション、自動入力
iOS のヒントはキーボードに合わせます:AutofillHints.email と TextInputType.emailAddress、AutofillHints.name と TextInputType.name。TextInputType。
TextInputAction.next は、同じ FocusScope 内の次のフォーカス可能な要素にフォーカスを移します。途中のフィールドには next を使用します。最後のフィールドには done(または go/search/send)を使用し、onFieldSubmitted で送信を処理します。iOS では previous や none を使用しないでください。TextInputAction。
autofillHints: const [AutofillHints.email] を渡してください。defaultTargetPlatform で分岐する場合を除き、独自の文字列は使用しないでください。null は渡さないでください(Android/web でフィールドが自動入力の対象外になります)。iOS と web では最初のヒントのみが使われ、Android ではリスト全体が使われます。
ログイン/登録/チェックアウトの各入力グループを AutofillGroup でラップしてください。クライアントはまとめてビルドする必要があります。まだレイアウトされていない ListView の子要素は登録されません。送信に成功したら、最後のステップの後に、画面ごとに一度だけ TextInput.finishAutofillContext(shouldSave: true) を呼び出してください。AutofillGroup。
フォーカス
FocusNode は initState で作成するか、一度だけ初期化する late フィールドとして作成してください。最初のフィールドには autofocus: true を設定します。バリデーションに失敗した後は、myFocusNode.requestFocus() を呼び出してください。FocusScope.of(context).requestFocus(focusNode) は呼び出さないでください。Focus cookbook。
デフォルトのタブ移動順序/読み上げ順序は ReadingOrderTraversalPolicy です。フォームを FocusTraversalGroup でラップしてください。視覚的な順序と移動順序が異なる場合:
FocusTraversalGroup(
policy: OrderedTraversalPolicy(),
child: FocusTraversalOrder(
order: const NumericFocusOrder(1),
child: emailField,
),
)
1 つのグループ内で比較できない FocusOrder の型を混在させると、アサーションエラーが発生します。Focus。
テーマ
ThemeData(
inputDecorationTheme: const InputDecorationThemeData(
filled: true,
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
floatingLabelBehavior: FloatingLabelBehavior.auto,
errorMaxLines: 2,
),
)
InputDecoration.applyDefaults は、このテーマから null の装飾フィールドのみを補完します。
避けること
GlobalKey<FormState>、FocusNode、またはTextEditingControllerをbuild内で割り当てないでください。Formの下に単独のTextFieldを置き、FormState.validate()で検証されると期待しないでください。- 非推奨の
autovalidate: trueを使ったり、AutovalidateMode.onFocusChangeを独自に作ったり、初回描画時に必須項目が空でもエラーになるようalwaysを設定したりしないでください。 validatorも使用しているのに、エラーをInputDecoration.errorTextだけで制御しないでください。autofillHintsを省略したり、nullに設定したり、新しいアカウントの作成フィールド(newPassword)にAutofillHints.passwordを使ったりしないでください。DropdownButtonFormField.valueを渡さずにinitialValueを渡してください。また、選択内容をフォームでvalidate()する必要がある場合は、生のDropdownMenuを使わないでください。- 長期間存続する、祖先が所有する
controllerを用意せず、TextFormFieldを遅延構築のListViewに配置しないでください。 - 各フィールドに
InputDecorationの枠線、色、パディングを丸ごとコピーして貼り付けないでください。 - コントローラーやフォーカスノードの
dispose()を省略したり、認証情報の送信成功後にTextInput.finishAutofillContextを呼び出さなかったりしないでください。 - ヘルパー行を確保せずにエラーテキストを表示しないでください。
確認
Formの下にあるTextField(、autovalidate:、AutovalidateMode.always、autofillHints: null、DropdownButtonFormFieldとvalue:の組み合わせ、およびbuild内で作成されているGlobalKey/FocusNode/TextEditingController(をgrepで検索します。- 検証対象のフィールドはすべて
FormFieldです。ログイン/サインアップの各まとまりは、それらの子が一緒に構築される単一のAutofillGroup内に配置します。 dart analyze/flutter analyzeで問題がないことを確認します。空欄、無効な値、サーバーエラー、キーボード表示中の状態をテストします。