1---2name: flutter-bloc-forms3description: Use when managing complex form state, synchronous and asynchronous input validation, or form submission transitions in BLoC.4---5
6# Form Architecture with BLoC
7
8- Manage form state in a dedicated `FormBloc` (not in widget `setState`)
9- Each form field maps to a property in the BLoC state
10- Validate on field change (real-time) or on submit (batch) depending on UX requirements
11- Emit `FormSubmitting`, `FormSuccess`, `FormError` states for submission flow
12
13## Form Events
14
15- `FieldChanged(field, value)`: update a single field in state
16- `FormSubmitted`: trigger validation and submission
17- `FormReset`: clear all fields and errors
18
19## Form State
20
21- Use a single state class with all field values, field-level errors, and form status:
22 ```dart
23 sealed class FormStatus { initial, submitting, success, failure }
24 ```
25- Field errors: `Map<String, String?>` keyed by field name (`null` means valid)
26
27# Validation Patterns
28
29- Validate in the domain layer (not in widgets or BLoCs)
30- Create pure validator functions that return `String?` (null = valid, string = error message):
31 ```dart
32 String? validateEmail(String value) =>
33 value.contains('@') ? null : 'Invalid email';
34 ```
35- Compose validators: `String? validate(String v) => validateRequired(v) ?? validateEmail(v)`
36- Use localized error messages via `context.l10n`: no hardcoded validation strings
37
38# Input Widgets
39
40- Use `TextFormField` with `InputDecoration` for consistent styling
41- Always set `textInputAction` for proper keyboard behavior (`next`, `done`)
42- Always set `keyboardType` matching the field type (`emailAddress`, `phone`, `number`)
43- Use `inputFormatters` to restrict input (e.g., `FilteringTextInputFormatter.digitsOnly`)
44- Assign `Key('feature_fieldName')` to every form field for test access
45- Use `AutofillHints` for login/signup forms (email, password, name)
46- Wrap form fields with `Focus` or `FocusTraversalGroup` for proper tab order
47
48# Controller Lifecycle
49
50- Declare `TextEditingController` as `late final` in `initState()`: dispose in `dispose()`
51- Sync controllers to BLoC via `onChanged` callback or controller listener
52
53# Form Submission
54
55- Disable submit button while `FormStatus.submitting` to prevent double-submission
56- Show inline field errors below each field (not just a top-level error)
57- On success: navigate, show success feedback, and reset form if staying on same page
58- On failure: show error feedback via `SnackBar` or inline, keep form data intact
59
60# Common Form Patterns
61
62- **Search**: Use `debounce` transformer on search events (300-500ms delay)
63- **Multi-step**: Each step is a separate form state within one `FormBloc`, validated independently
64- **Dependent fields**: Update dependent field options in `on<FieldChanged>` handler (e.g., country → city)