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.

Libraries

form_engine