auto_validated_form 0.0.1
auto_validated_form: ^0.0.1 copied to clipboard
A Flutter package for reactive, granular form validation with smart timing, state tracking, and selective widget rebuilding.
auto_validated_form #
A reactive, granular, and boilerplate-free form validation package for Flutter.
Note #
This package is still under development, so if you're looking for a more mature and reliable solution with similar functionality, take a look at reactive_forms.
Demo #
Why auto_validated_form? #
Form validation in Flutter often requires frustrating compromises:
- Keystroke validation is annoying: Validating immediately on the first keystroke (
AutovalidateMode.always) complains before the user even finishes typing. - Unfocus validation is too late: Validating only on unfocus or submit makes it difficult to reactively enable or disable a submit button as soon as the user enters valid data.
- Rebuilding the whole tree: Standard reactive forms often require listening to controllers with
setStateor rebuilding entire screens, causing unnecessary re-renders.
auto_validated_form solves this with a smart validation lifecycle:
- Intelligent Triggering: Untouched fields validate when losing focus. Once touched, they validate smoothly as the user types.
- Granular Reactivity: Submit buttons and error displays rebuild only when needed without re-rendering all your input fields.
- Clean Architecture: Decouple validation state tracking from your presentation widgets using
ValidationStateTrackerandAutoValidatedFormScope.
Features #
- ⚡ Smart Validation Lifecycle: Unobtrusive on blur when empty, live on keystroke once edited.
- 🎯 Targeted Reactive Rebuilds: Use
AutoValidatedFormBuilderto rebuild only the submit button or error views. - 🔌 Drop-in
TextFieldReplacement:AutoValidatedTextFieldsupports all standard FlutterTextFieldstyling and behavior. - 🛠️ Custom Input Support: Wrap any custom widget with
AutoValidatedField. - 📊 Powerful State Helpers: Easily query
areAllValid,errorMessages,hasErrors, orerrorsMap. - 🔄 Form Resetting: Reset all fields to their clean initial state in a single call.
Getting Started #
Add auto_validated_form to your pubspec.yaml:
dependencies:
auto_validated_form: ^0.0.1
Or install it via terminal:
flutter pub add auto_validated_form
Import the package in your Dart code:
import 'package:auto_validated_form/auto_validated_form.dart';
Quick Start #
Setting up an auto-validated form takes 4 easy steps:
import 'package:auto_validated_form/auto_validated_form.dart';
import 'package:flutter/material.dart';
class QuickStartForm extends StatefulWidget {
const QuickStartForm({super.key});
@override
State<QuickStartForm> createState() => _QuickStartFormState();
}
class _QuickStartFormState extends State<QuickStartForm> {
// 1. Initialize the tracker with your field IDs
late final ValidationStateTracker _tracker;
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
void initState() {
super.initState();
_tracker = ValidationStateTracker.defaultFalse(['email', 'password']);
}
@override
void dispose() {
_tracker.dispose();
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// 2. Wrap your form in AutoValidatedFormScope
return Scaffold(
body: AutoValidatedFormScope(
validationStateTracker: _tracker,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
// 3. Use AutoValidatedTextField
AutoValidatedTextField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
validationListener: _tracker.validationListener('email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
return 'Please enter a valid email address';
}
return null;
},
),
const SizedBox(height: 16),
AutoValidatedTextField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(labelText: 'Password'),
validationListener: _tracker.validationListener('password'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
if (value.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
},
),
const SizedBox(height: 24),
// 4. Reactively enable the submit button
AutoValidatedFormBuilder(
builder: (context, fields, isValid, child) {
return ElevatedButton(
onPressed: isValid ? _submit : null,
child: child!,
);
},
child: const Text('Submit'),
),
],
),
),
),
);
}
void _submit() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Form Submitted!')),
);
}
}
Practical Recipes #
1. Auto-Validated Submit Button (Active / Inactive) #
Keep your submit button disabled (inactive) until all form fields are valid, and immediately activate it as soon as validation passes—without re-rendering any of your input fields:
AutoValidatedFormBuilder(
// Pass static children to prevent unnecessary rebuilds
child: const Text('Submit Application'),
builder: (context, fields, isValid, child) {
return ElevatedButton(
// When isValid is true, button is enabled.
// Setting onPressed to null automatically disables (deactivates) Flutter buttons.
onPressed: isValid ? () => _submitForm() : null,
style: ElevatedButton.styleFrom(
backgroundColor: isValid ? Theme.of(context).primaryColor : Colors.grey.shade400,
foregroundColor: Colors.white,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(isValid ? Icons.check_circle_outline : Icons.lock_outline, size: 18),
const SizedBox(width: 8),
child!,
],
),
);
},
)
Tip: Both
isValidandfields.areAllValidprovide the form-wide validity status. In standard Flutter buttons (ElevatedButton,FilledButton,TextButton,OutlinedButton), passingnulltoonPressedautomatically triggers the platform's disabled styling and touch rejection.
2. Centralized Error Summary List #
You can display all active errors in one place (e.g., at the top or bottom of a form) using fields.errorMessages:
AutoValidatedFormBuilder(
builder: (context, fields, isValid, child) {
final errors = fields.errorMessages;
if (errors.isEmpty) return const SizedBox.shrink();
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (final msg in errors)
Text('• $msg', style: const TextStyle(color: Colors.red)),
],
),
);
},
)
3. Custom Input Widgets with AutoValidatedField #
If you are not using standard TextField widgets (or want complete control over how errors are rendered), use AutoValidatedField:
AutoValidatedField(
controller: _customController,
validator: (val) => val == null || val.isEmpty ? 'Field required' : null,
validationListener: _tracker.validationListener('custom_field'),
fieldBuilder: (context, errorText) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomInputBox(controller: _customController),
if (errorText.isNotEmpty)
Text(errorText, style: const TextStyle(color: Colors.red)),
],
);
},
)
4. Resetting Form State #
To clear your inputs and restore the tracker to its initial unvalidated state:
void _resetForm() {
_emailController.clear();
_passwordController.clear();
_tracker.reset();
}
5. Custom Error Presentation on AutoValidatedTextField #
You can hide inline error text or supply a custom error widget builder:
AutoValidatedTextField(
controller: _controller,
validationListener: _tracker.validationListener('field'),
validator: (val) => val == null || val.isEmpty ? 'Required' : null,
// Custom error widget
errorWidgetBuilder: (errorText) => Text(
'⚠️ $errorText',
style: const TextStyle(color: Colors.deepOrange, fontSize: 12),
),
// Or hide the inline error entirely:
// hideError: true,
)
Architecture & API Overview #
| Component | Role |
|---|---|
ValidationStateTracker |
Central controller holding reactive validation states (ValueNotifier<Map<String, FieldModel>>). |
AutoValidatedFormScope |
InheritedWidget that makes the ValidationStateTracker accessible to all descendant widgets. |
AutoValidatedTextField |
Drop-in TextField that wires itself to validation lifecycle listeners automatically. |
AutoValidatedField |
Generic wrapper accepting any custom widget via fieldBuilder(context, errorText). |
AutoValidatedFormBuilder |
Reactive builder that rebuilds only when form validity or field states change. |
FieldModelMapExtension |
Convenience getters on Map<String, FieldModel> (areAllValid, errorMessages, errorsMap, hasErrors). |
AutoValidatedFormController |
Imperative controller for triggering validation manually when needed. |
ValidationStateTracker Constructors #
ValidationStateTracker.defaultFalse(List<String> ids): Initializes specified field IDs with unvalidated states.ValidationStateTracker.byList(List<FieldModel> fields): Initializes tracker with a list of customFieldModelinstances.ValidationStateTracker.byFields(Map<String, FieldModel> fields): Initializes tracker with an explicit map ofFieldModelinstances.
Comparison: Traditional Flutter vs. auto_validated_form #
Check out the example/ directory for a complete, runnable app demonstrating:
- Side-by-side comparison between Flutter's default
Form/TextFormFieldapproach andauto_validated_form. - How
auto_validated_formeliminates boilerplate listener chains and timing delays.
To run the example app:
cd example
flutter run
Contributing #
Contributions, issues, and feature requests are welcome!
Feel free to open an issue or pull request on GitHub.
License #
This project is licensed under the MIT License - see the LICENSE file for details.