form_engine
A headless, controller-free, declarative form engine for Flutter & Dart.
form_engine lets you manage form state and validation without widgets, TextEditingControllers, or tight coupling to any UI or state-management framework.
It is built for clean architecture, testability, and long-term maintainability.
Why form_engine?
Most Flutter form libraries are:
- Widget-first
- Controller-heavy
- Hard to unit test
- Tightly coupled to UI
- Difficult to use in clean / layered architectures
form_engine takes a different approach.
It is:
- ๐ง State-first
- ๐งฉ UI-agnostic
- ๐งช Pure Dart core
- ๐ Clean-architecture friendly
You define form behavior, not widget trees.
Key Features
- Headless form engine (no widgets required)
- Controller-free API
- Declarative form & field schemas
- Fluent, composable validation DSL
- Centralized, immutable form state
- Easy unit testing (no widget tests needed)
- Works with
setState, Bloc, Cubit, Riverpod, MVC, MVVM
Installation
dependencies:
form_engine: ^0.0.1
Defining a Form Schema
final schema = FormSchema(
fields: {
'email': FieldSchema<String>.withValidators(
validators: Validators()
.required()
.email(),
),
'password': FieldSchema<String>.withValidators(
validators: Validators()
.required()
.minLength(6),
),
},
);
Create the engine
final form = FormEngine(schema: schema);
Update Values (No Controllers)
form.updateValue('email', 'test@example.com');
form.updateValue('password', '123456');
Values are pushed directly into the engine โ no TextEditingController lifecycle management.
Validate and read state
final isValid = form.validateAll();
form.state.values; // Map<String, dynamic>
form.state.errors; // Map<String, String>
form.state.isValid; // bool
The exposed state is read-only and safe to use anywhere.
Validators DSL
form_engine ships with a rich string validators pack.
Available Validators
Validators()
.required()
.email()
.minLength(6)
.maxLength(20)
.numeric()
.url()
.alpha()
.alphaSpace()
.regex(RegExp(r'^\d{10}$'));
Each validator:
- Is composable
- Supports custom error messages
- Is fully unit tested
- Can be reused across forms
๐ Controller-Free Registration Form Example
This example demonstrates how to build a real-world registration form using form_engine โ without TextEditingControllers, widget-level validation, or UI-coupled logic.
Define the Form Schema
final form = FormEngine(
schema: FormSchema(
fields: {
'fullName': FieldSchema<String>.withValidators(
validators: Validators()
.required()
.alphaSpace()
.minLength(3)
.maxLength(50),
),
'username': FieldSchema<String>.withValidators(
validators: Validators()
.required()
.alpha()
.minLength(3)
.maxLength(20),
),
'email': FieldSchema<String>.withValidators(
validators: Validators()
.required()
.email(),
),
'phone': FieldSchema<String>.withValidators(
validators: Validators()
.required()
.numeric()
.minLength(10)
.maxLength(10),
),
'password': FieldSchema<String>.withValidators(
validators: Validators()
.required()
.minLength(8)
.regex(
RegExp(r'^(?=.*[A-Z])(?=.*\d).*$'),
message: 'Must contain an uppercase letter and a number',
),
),
'website': FieldSchema<String>.withValidators(
validators: Validators().url(),
),
},
),
);
Update Values (No Controllers)
TextField(
onChanged: (value) {
form.updateValue('email', value);
setState(() {});
},
decoration: InputDecoration(
labelText: 'Email',
errorText: form.state.errors['email'],
),
);
Validate & Submit
final isValid = form.validateAll();
if (isValid) {
print(form.state.values);
}
Testing Forms (Pure Dart)
Because form_engine has no widget dependency, testing is simple and fast:
test('email validation fails for invalid input', () {
form.updateValue('email', 'invalid');
form.validateAll();
expect(form.state.errors['email'], isNotNull);
});
- no pumpWidget
- No fake contexts.
- No controllers
Architecture Friendly
form_engine is designed for:
- Clean Architecture
- Domain / Application layers
- Enterprise Flutter apps
- Long-lived codebases
It fits naturally into:
- Bloc / Cubit
- Riverpod / Provider
- MVC / MVVM
- setState-based apps
Your UI becomes a consumer of state, not the owner of form logic.
When to Use form_engine
Use form_engine if you want:
- Business-logic-driven forms
- Controller-free validation
- Easy unit testing
- UI-agnostic form state
- Clean separation of concerns
Roadmap
Planned improvements:
- Cross-field validation (e.g. matches('password'))
- Async validators (server-side checks)
- Numeric range validators
- Typed value adapters
- Better tooling & documentation
Philosophy
Forms are domain logic, not widget trees.
form_engine treats them that way.