Skip to content

Without Codegen

You can use validasi_ui without annotations or build_runner. Define ValidasiField instances manually and register them with the form controller. This is useful for quick prototypes, dynamic forms, or when you prefer explicit control over generated code.

Manual field definition

dart
import 'package:validasi/validasi.dart';
import 'package:validasi_ui/validasi_ui.dart';

class User {
  final String name;
  final String email;

  const User({required this.name, required this.email});
}

// Define fields manually
final nameField = ValidasiField<User, String>(
  name: 'name',
  extract: (user) => user.name,
  validate: Validasi.string([Rules.string.minLength(2)]).validate,
);

final emailField = ValidasiField<User, String>(
  name: 'email',
  extract: (user) => user.email,
  validate: Validasi.string([Rules.string.minLength(5)]).validate,
);

ValidasiField<T, V> takes three required parameters:

ParameterDescription
nameUnique field identifier (for error paths)
extractV? Function(T) — reads the field value from a model instance
validateValidasiResult<V> Function(V?) — the validation pipeline

Defining a schema

dart
final userSchema = ValidasiSchema<User>(
  allocate: (reader) => User(
    name: reader.getValue(nameField) as String,
    email: reader.getValue(emailField) as String,
  ),
);

Form widget

dart
ValidasiForm<User>(
  schema: userSchema,
  builder: (context, submit) => Column(
    children: [
      ValidasiFormField<User, String>(
        field: nameField,
        builder: (context, state) => TextField(
          onChanged: state.onChanged,
          decoration: InputDecoration(
            labelText: 'Name',
            errorText: state.errorText,
          ),
        ),
      ),
      ValidasiFormField<User, String>(
        field: emailField,
        builder: (context, state) => TextField(
          onChanged: state.onChanged,
          decoration: InputDecoration(
            labelText: 'Email',
            errorText: state.errorText,
          ),
        ),
      ),
      ElevatedButton(
        onPressed: submit((user) {
          print('Saved: ${user.name}');
        }),
        child: const Text('Submit'),
      ),
    ],
  ),
);

Using ValidasiTextField manually

dart
ValidasiTextField<User, String>(
  field: nameField,
  builder: (context, state, controller) => TextField(
    controller: controller,
    decoration: InputDecoration(
      labelText: 'Name',
      errorText: state.errorText,
    ),
  ),
),

ValidasiTextField automatically handles TextEditingController creation, form-value sync, and disposal — just pass controller to your TextField.

Manual ValidasiField with async validation

dart
final emailField = ValidasiField<User, String>(
  name: 'email',
  extract: (user) => user.email,
  validate: Validasi.string([Rules.string.minLength(5)]).validate,
  validateAsync: (value) async {
    // Runs after sync validation
    if (value != null && value.isNotEmpty) {
      final available = await checkEmailAvailability(value);
      if (!available) {
        return ValidasiResult.error(
          ValidationError(rule: 'email', message: 'Email already taken'),
        );
      }
    }
    return ValidasiResult.success(value);
  },
);

Pass validateAsync to define async validation for a manual field. The form controller runs it when validateAsync() is called.

When to skip codegen

  • Prototyping — iterate on form structure without running build_runner every change.
  • Dynamic forms — form fields are determined at runtime (e.g. from a JSON schema).
  • Small apps — a handful of fields where the overhead of annotations isn't worth it.
  • Non-Dart models — the model class is defined in another language or generated by an external tool.

When to use codegen

  • Type safety — generated XFields<V> ensures field and value types match.
  • ErgonomicsUserFields.name instead of manually tracking const field objects.
  • Maintainability — add a field to the model and build_runner emits everything.
  • Cross-field@RefineFn is only available through codegen.
  • Refactoring — rename a field and the generated code follows.

Released under the MIT License.