flutter-form
Flutter Form Validation
Goal
Implements stateful form validation in Flutter using Form, TextFormField, and GlobalKey<FormState>. Manages validation state efficiently without unnecessary key regeneration and handles user input validation workflows. Assumes a pre-existing Flutter environment with Material Design dependencies available.
Decision Logic
When implementing form validation, follow this decision tree to determine the flow of state and UI updates:
- User triggers submit action:
- Call
_formKey.currentState!.validate().
- Call
- Does
validate()returntrue?- Yes (Valid): Proceed with data processing (e.g., API call, local storage). Trigger success UI feedback (e.g.,
SnackBar, navigation). - No (Invalid): The
FormStateautomatically rebuilds theTextFormFieldwidgets to display theStringerror messages returned by their respectivevalidatorfunctions. Halt submission.
- Yes (Valid): Proceed with data processing (e.g., API call, local storage). Trigger success UI feedback (e.g.,
Instructions
-
Initialize the Stateful Form Container Create a
StatefulWidgetto hold the form. Instantiate aGlobalKey<FormState>exactly once within theStateclass to prevent resource-expensive key regeneration duringbuildcycles.import 'package:flutter/material.dart'; class CustomValidatedForm extends StatefulWidget { const CustomValidatedForm({super.key}); State<CustomValidatedForm> createState() => _CustomValidatedFormState(); } class _CustomValidatedFormState extends State<CustomValidatedForm> { // Instantiate the GlobalKey once in the State object final _formKey = GlobalKey<FormState>(); Widget build(BuildContext context) { return Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ // Form fields will be injected here ], ), ); } } -
Implement TextFormFields with Validation Logic Inject
TextFormFieldwidgets into theForm's widget tree. Provide avalidatorfunction for each field.TextFormField( decoration: const InputDecoration( hintText: 'Enter your email', labelText: 'Email', ), validator: (String? value) { if (value == null || value.isEmpty) { return 'Please enter an email address'; } if (!value.contains('@')) { return 'Please enter a valid email address'; } // Return null if the input is valid return null; }, onSaved: (String? value) { // Handle save logic here }, ) -
Implement the Submit Action and Validation Trigger Create a button that accesses the
FormStatevia theGlobalKeyto trigger validation.Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: ElevatedButton( onPressed: () { // Validate returns true if the form is valid, or false otherwise. if (_formKey.currentState!.validate()) { // Save the form fields if necessary _formKey.currentState!.save(); // Provide success feedback ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Processing Data')), ); } }, child: const Text('Submit'), ), ) -
STOP AND ASK THE USER: Pause implementation and ask the user for the following context:
- "What specific fields do you need in this form?"
- "What are the exact validation rules for each field (e.g., regex patterns, minimum lengths)?"
- "What action should occur upon successful validation (e.g., API payload submission, navigation)?"
-
Validate-and-Fix Loop After generating the form, verify the following:
- Ensure
_formKey.currentState!.validate()is null-checked properly using the bang operator (!) or safe calls if the key might be detached. - Verify that every
validatorfunction explicitly returnsnullon success. Returning an empty string ("") will trigger an error state with no text.
- Ensure
Constraints
- DO NOT instantiate the
GlobalKey<FormState>inside thebuildmethod. It must be a persistent member of theStateclass. - DO NOT use a
StatelessWidgetfor the form container unless theGlobalKeyis being passed down from a stateful parent. - DO NOT use standard
TextFieldwidgets if you require built-in form validation; you must useTextFormField(which wrapsTextFieldin aFormField). - ALWAYS return
nullfrom avalidatorfunction when the input is valid. - ALWAYS ensure the
Formwidget is a common ancestor to allTextFormFieldwidgets that need to be validated together.