Comprehensive guide to Flutter forms, validation, gestures, and input handling
From flutter-corenpx claudepluginhub aaronbassett/agent-foundry --plugin flutter-coreThis skill uses the workspace's default tool permissions.
examples/complex-forms.mdexamples/custom-gestures.mdreferences/form-widgets.mdreferences/gesture-detection.mdreferences/keyboard-management.mdreferences/validation-patterns.mdSearches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides implementation of event-driven hooks in Claude Code plugins using prompt-based validation and bash commands for PreToolUse, Stop, and session events.
Master Flutter's form system, validation strategies, gesture detection, and input management to create interactive, user-friendly applications.
Flutter provides a comprehensive system for handling user input through forms, text fields, validation, gestures, and focus management. This skill covers the complete spectrum of input handling, from simple text fields to complex multi-step forms with validation, and from basic tap detection to custom gesture recognizers.
Use this skill when you need to:
Flutter's form system is built on several key components that work together:
Form Widget: The container that manages the state of multiple form fields. It uses a GlobalKey<FormState>() to access validation and submission methods.
TextFormField: The primary input widget for forms, integrating text input with validation. It automatically registers with parent Form widgets and participates in form-wide validation.
FormState: The state object that provides methods like validate(), save(), and reset(). Access it through the form's GlobalKey.
Validators: Functions that return error messages for invalid input or null for valid input.
Flutter's gesture system operates on two layers:
Pointer Events: Raw data about touch, mouse, or stylus interactions (PointerDownEvent, PointerMoveEvent, PointerUpEvent, PointerCancelEvent).
Gestures: Semantic actions recognized from pointer events (taps, drags, scales, swipes).
The gesture system uses a competitive arena where multiple gesture recognizers compete to claim input events. This allows sophisticated gesture handling without conflicts.
The focus system directs keyboard input to specific widgets:
FocusNode: A long-lived object that holds focus state for a widget. Must be created in State and disposed properly.
FocusScope: Groups focus nodes and manages focus history within a subtree.
Focus Widget: Owns and manages a FocusNode, providing callbacks for focus changes and key events.
class MyForm extends StatefulWidget {
@override
State<MyForm> createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nameController,
decoration: InputDecoration(labelText: 'Name'),
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Name is required';
}
return null;
},
),
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Email is required';
}
if (!value!.contains('@')) {
return 'Invalid email';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Process form
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Processing...')),
);
}
},
child: Text('Submit'),
),
],
),
);
}
}
Synchronous Validation: Immediate validation during user input or on submission. Used for format checking, required fields, and simple business rules.
Asynchronous Validation: Validation that requires external checks (API calls, database lookups). Implement with FutureBuilder or state management.
Real-time vs On-Submit: Choose autovalidateMode based on UX needs:
AutovalidateMode.disabled: Validate only on submit (default)AutovalidateMode.onUserInteraction: Validate after first interactionAutovalidateMode.always: Validate on every change (can be annoying)Use TextEditingController to:
Always dispose controllers in the dispose() method to prevent memory leaks.
GestureDetector(
onTap: () => print('Tapped'),
onDoubleTap: () => print('Double tapped'),
onLongPress: () => print('Long pressed'),
onPanUpdate: (details) {
// Handle drag
print('Delta: ${details.delta}');
},
child: Container(
width: 200,
height: 200,
color: Colors.blue,
),
)
InkWell(
onTap: () => print('Tapped with ripple'),
splashColor: Colors.blue.withOpacity(0.3),
child: Container(
padding: EdgeInsets.all(16),
child: Text('Tap me'),
),
)
Avoid mixing conflicting gestures:
onPanUpdate with onVerticalDragUpdate or onHorizontalDragUpdateclass _MyWidgetState extends State<MyWidget> {
late FocusNode _focusNode;
@override
void initState() {
super.initState();
_focusNode = FocusNode(debugLabel: 'MyWidget');
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Focus(
focusNode: _focusNode,
onFocusChange: (focused) {
setState(() {
// Update UI based on focus
});
},
child: TextField(),
);
}
}
// Request focus
_focusNode.requestFocus();
// Remove focus
_focusNode.unfocus();
// Check focus state
bool hasFocus = _focusNode.hasFocus;
// Move to next field
FocusScope.of(context).nextFocus();
// Move to previous field
FocusScope.of(context).previousFocus();
Configure keyboard action buttons:
TextFormField(
textInputAction: TextInputAction.next, // Shows "Next" button
onFieldSubmitted: (value) {
FocusScope.of(context).nextFocus(); // Move to next field
},
)
TextFormField(
textInputAction: TextInputAction.done, // Shows "Done" button
onFieldSubmitted: (value) {
FocusScope.of(context).unfocus(); // Close keyboard
},
)
TextFormField(
keyboardType: TextInputType.emailAddress,
// Other options: number, phone, url, datetime, text, multiline
)
Use a PageView or stepper widget with separate Form widgets for each step. Validate each step before allowing progression.
Use ListView.builder with a list of field configurations. Add/remove fields by modifying the list and calling setState.
Listen to controller changes and debounce saves to avoid excessive operations.
Create reusable validator functions:
String? Function(String?) combineValidators(
List<String? Function(String?)> validators,
) {
return (value) {
for (final validator in validators) {
final error = validator(value);
if (error != null) return error;
}
return null;
};
}
Combine GestureDetector with AnimatedContainer or Transform widgets to provide visual feedback during gestures.
Form doesn't validate: Ensure Form has a GlobalKey and you're calling _formKey.currentState?.validate().
TextEditingController not updating: Make sure you're not creating a new controller in build method.
Gestures not detected: Check that callbacks are defined and no conflicting gestures exist.
Focus not working: Verify FocusNode is created in State and disposed properly.
Keyboard doesn't show: Ensure TextField is focusable and not blocked by canRequestFocus: false.
Memory leaks: Always dispose TextEditingController and FocusNode in dispose method.