mcpField
F
flutter-building-forms

flutter-building-forms

github.com/thesimpleapp開発052 回インストール11 週間前に更新
リポジトリ

flutter-building-forms について

Flutter 開発者向けに、Form のフィールド、バリデーション、フォーカス、キーボード、自動入力の実装方針を定めるスキルです。既存のテーマや構成を活用し、送信やエラー処理、検証方法も扱います。

主な機能

  1. 01Form 対応フィールドの選択
  2. 02検証とサーバーエラーの処理
  3. 03キーボード・フォーカス・自動入力の設定
  4. 04テーマへの準拠とエラー領域の確保
  5. 05フォーム固有の検証手順

活用シーン

  1. 01ログインまたは登録フォームの構築
  2. 02ドロップダウン選択の検証
  3. 03サーバー側フィールドエラーの表示
  4. 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

必須ルール

  1. State が final GlobalKey<FormState> _formKey を所有します。build 内でキー、FocusNode、TextEditingController を生成しないでください。送信処理では、if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); … } を使います。送信に失敗した場合は、validateGranularly() を実行してから、視覚的な順序で最初の無効なフィールドに対して FocusNode.requestFocus() を呼び出します(Set は順序付けされていません)。
  2. Form 内では、検証または保存が必要な値はすべて TextFormField、DropdownMenuFormField、またはカスタムの FormField<T> にします。通常の TextField は使わないでください。
  3. validator の型は FormFieldValidator<T>? で、String? を返します(null は有効を意味します)。処理は軽量かつ同期的に保ちます。API / サーバーのエラーには forceErrorText を使います。
  4. autovalidateMode の既定値は AutovalidateMode.disabled です。最初の送信後は onUserInteraction または onUnfocus を推奨します。必須かつ空のフィールドに always を使わないでください。AutovalidateMode.onFocusChange はありません。onUserInteractionIfError も使用できます。
  5. ThemeData.inputDecorationTheme(InputDecorationThemeData)でスタイルを設定します。フィールドごとの InputDecoration では labelText / hintText / helperText / prefixIcon のみを設定します。
  6. すべてのフィールドに keyboardType と textInputAction を設定します(最後のフィールドまでは next、最後は done / go / send と onFieldSubmitted を使います)。
  7. autofillHints: const [AutofillHints.…] を設定します。ログインでは username と password、サインアップでは newUsername と newPassword を使います。ほかに email、oneTimeCode もあります。フィールド群を AutofillGroup で囲みます。送信に成功したら、TextInput.finishAutofillContext(shouldSave: true) を 1 回呼び出します。複数ステップのフォームでは、最後のステップの後に呼び出します。
  8. コントローラーと FocusNode は State 上で一度だけ作成し、dispose() で破棄します。遅延読み込みの ListView / CustomScrollView では、コントローラーをステートフルな祖先が所有する必要があります。
  9. errorText の表示でフォームが再配置されないよう、ヘルパー / エラー領域を確保します(helperText: ' ' または高さを固定した親を使います)。
  10. Form 内の新しいドロップダウンには DropdownMenuFormField を使います。DropdownButtonFormField を引き続き使う場合は、非推奨の value ではなく initialValue を渡します。
  11. 最初のフィールドに autofocus を設定します。フォームを FocusTraversalGroup で囲みます。視覚的な順序と読み上げ順序が異なる場合に限り、OrderedTraversalPolicy と NumericFocusOrder を使います。
  12. 操作可能なフィールドには 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:

値実行タイミング
disabledvalidate/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 を呼び出さなかったりしないでください。
  • ヘルパー行を確保せずにエラーテキストを表示しないでください。

確認

  1. Form の下にある TextField(、autovalidate:、AutovalidateMode.always、autofillHints: null、DropdownButtonFormField と value: の組み合わせ、および build 内で作成されている GlobalKey / FocusNode / TextEditingController( を grep で検索します。
  2. 検証対象のフィールドはすべて FormField です。ログイン/サインアップの各まとまりは、それらの子が一緒に構築される単一の AutofillGroup 内に配置します。
  3. dart analyze / flutter analyze で問題がないことを確認します。空欄、無効な値、サーバーエラー、キーボード表示中の状態をテストします。

コメント