mobikul_flutter_form_validator 0.0.1 copy "mobikul_flutter_form_validator: ^0.0.1" to clipboard
mobikul_flutter_form_validator: ^0.0.1 copied to clipboard

Enterprise-ready Flutter form validation foundation with planned sync, async, localized, and extensible validators.

Mobikul Flutter Form Validator #

Professional Flutter form validation package with built-in validators, native TextFormField support, async validation primitives, cross-field validation, localization-ready messages, accessibility summaries, and dynamic form validation.

To find out more, visit: https://mobikul.com/

Overview #

mobikul_flutter_form_validator provides a reusable validation layer for Flutter apps.

It exposes:

  • Validators for sync validation rules.
  • AsyncValidators for async validation rules.
  • ValidationResult and ValidationError for structured validation output.
  • ValidationErrorFormatter for user-facing error text.
  • ValidationMessageResolver for localization.
  • ValidationAccessibilitySummary for accessible error summaries.
  • DynamicValidationForm for runtime field registration.
  • toFlutterValidator() for native TextFormField.validator usage.

The package currently includes form_builder_validators-style validator factory parity, plus additional package-specific helpers such as password policy validation, cross-field matching, async validation runner, dynamic forms, and accessibility summaries.

Requirements #

  • A Flutter project.
  • A Dart SDK version compatible with this package's environment.sdk constraint.
  • No required form framework dependency. The package works with Flutter's native TextFormField.

Quick Start #

  1. Install the package.
  2. Import mobikul_flutter_form_validator.
  3. Add a validator to TextFormField.validator.
  4. Use Validators.compose<T>() when a field needs more than one rule.
import 'package:mobikul_flutter_form_validator/mobikul_flutter_form_validator.dart';
TextFormField(
  decoration: const InputDecoration(labelText: 'Email'),
  validator: Validators.compose<String>([
    Validators.required<String>(),
    Validators.email(),
  ]).toFlutterValidator(),
)

Example App #

A runnable Flutter example is available in example/. It depends on this package through a local path dependency, the same way pub.dev examples are validated before publishing.

Run it with:

cd example
flutter pub get
flutter run

The example demonstrates:

  • Native TextFormField validation with toFlutterValidator().
  • Required, email, password, and cross-field confirm-password rules.
  • Structured validation data through DynamicValidationForm.
  • Accessibility summary data for invalid dynamic fields.

Installation #

Add the package to your Flutter app:

dependencies:
  mobikul_flutter_form_validator: ^0.0.1

Then run:

flutter pub get

Usage #

1. Required Validator #

Use Validators.required<T>() when a field must not be empty. This is useful for names, email addresses, passwords, addresses, and any field that must be submitted with a value.

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Name'),
  validator: Validators.required<String>().toFlutterValidator(),
)

How it works:

  • Invalid: '', ' '
  • Valid: 'John Doe',

Preview:

Required validator invalid preview Required validator valid preview

2. Email Validator #

Use Validators.email() when a field must contain a valid email format. Email validation is commonly used in login, registration, checkout, contact, and account update forms.

Validators.email() only checks email format. Empty values are valid by default, so combine it with Validators.required<String>() when the email field is mandatory.

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Email'),
  validator: Validators.compose<String>([
    Validators.required<String>(),
    Validators.email(),
  ]).toFlutterValidator(),
)

How it works:

  • Invalid: '', 'usergmail.com', 'user@', 'user@example'
  • Valid: 'user@gmail.com', 'customer@example.com'

Real-world case:

Use this in a signup form where the user must provide an email that can receive account verification messages.

Preview:

Email validator invalid preview Email validator valid preview

3. Password Validator #

Use Validators.password() when a password must follow a security policy. You can configure minimum length, uppercase, lowercase, number, and special-character rules.

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Password'),
  obscureText: true,
  validator: Validators.compose<String>([
    Validators.required<String>(),
    Validators.password(
      minLength: 8,
      requireUppercase: true,
      requireLowercase: true,
      requireNumber: true,
      requireSpecialCharacter: true,
    ),
  ]).toFlutterValidator(),
)

How it works for the policy above:

  • Invalid: '', 'pass', 'password', 'Password', 'Password1'
  • Valid: 'Password1!', 'Secure@123'

Real-world case:

Use this in registration or reset-password forms to prevent weak passwords before the request reaches your server.

Preview:

Password validator invalid preview Password validator valid preview

4. Regex Validator #

Use Validators.regex() or Validators.pattern() when a field must follow a custom format. This is useful for usernames, referral codes, coupon codes, SKU values, or internal IDs.

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Username'),
  validator: Validators.compose<String>([
    Validators.required<String>(),
    Validators.regex(
      RegExp(r'[a-z0-9_]+'),
      message: 'Use lowercase letters, numbers, or underscores',
    ),
  ]).toFlutterValidator(),
)

How it works for the username rule above:

  • Invalid: '', 'John Doe', 'User@123', 'john-doe'
  • Valid: 'john_doe', 'user123', 'customer_01'

The validator uses full-string matching by default, so the entire value must match the pattern.

Preview:

Regex validator invalid preview Regex validator valid preview

5. URL Validator #

Use Validators.url() when a field must contain a valid website URL. This is useful for profile websites, company URLs, webhook URLs, and support links.

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Website'),
  validator: Validators.compose<String>([
    Validators.required<String>(),
    Validators.url(),
  ]).toFlutterValidator(),
)

How it works:

  • Invalid: '', 'example', 'example.com', 'ftp://example.com'
  • Valid: 'https://example.com', 'http://example.com/help'

Preview:

URL validator invalid preview URL validator valid preview

6. Numeric Range Validator #

Use Validators.numeric(), Validators.min(), and Validators.max() when a field must be a number inside a valid range. This is useful for age, quantity, price, percentage, discount, and rating fields.

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Age'),
  keyboardType: TextInputType.number,
  validator: Validators.compose<Object>([
    Validators.required<Object>(),
    Validators.numeric(),
    Validators.min(18),
    Validators.max(99),
  ]).toFlutterValidator(),
)

How it works for the age rule above:

  • Invalid: '', 'abc', '17', '100'
  • Valid: '18', '24', '99'

Preview:

Numeric range validator minimum invalid preview Numeric range validator maximum invalid preview Numeric range validator valid preview

7. Cross-Field Validator #

Use Validators.matchesField<T>() when one field must match another field.

Common examples are confirm password, confirm email, repeat PIN, and date fields that depend on another value.

Implementation:

final passwordController = TextEditingController();

TextFormField(
  controller: passwordController,
  decoration: const InputDecoration(labelText: 'Password'),
  obscureText: true,
)

TextFormField(
  decoration: const InputDecoration(labelText: 'Confirm password'),
  obscureText: true,
  validator: Validators.compose<String>([
    Validators.required<String>(),
    Validators.matchesField<String>(
      'password',
      message: 'Passwords must match',
    ),
  ]).toFlutterValidator(
    contextBuilder: () => ValidationContext(
      values: {'password': passwordController.text},
    ),
  ),
)

How the code works:

  • passwordController stores the current password field value.
  • The confirm password field validates its own value.
  • Validators.required<String>() makes sure confirm password is not empty.
  • Validators.matchesField<String>('password') compares the confirm password value against a value named password.
  • contextBuilder runs at validation time, so it always reads the latest passwordController.text.
  • ValidationContext(values: {'password': passwordController.text}) passes the password value into the validator.
  • If confirm password and password are different, the validator returns Passwords must match.

Example behavior:

  • Password: 'Secret123!', Confirm password: 'Secret123?' -> invalid
  • Password: 'Secret123!', Confirm password: 'Secret123!' -> valid

Preview:

Cross-field validator invalid preview Cross-field validator valid preview

8. Async Validator #

Use AsyncValidators.custom<T>() with AsyncValidationRunner<T> when validation needs an async check. This is useful for username availability, email availability, invite code verification, or server-backed business rules.

Implementation:

final runner = AsyncValidationRunner<String>();

final usernameAvailable = AsyncValidators.custom<String>(
  key: 'usernameTaken',
  debounce: const Duration(milliseconds: 400),
  validate: (value, context) async {
    final isAvailable = value != 'admin';

    return isAvailable
        ? const ValidationResult.valid()
        : ValidationResult.invalidKey(
            'usernameTaken',
            message: 'Username is already taken',
            source: ValidationErrorSource.async,
          );
  },
);

final outcome = await runner.validate(
  usernameAvailable,
  'admin',
  const ValidationContext(fieldKey: 'username'),
);

if (outcome.shouldApply && outcome.result.isInvalid) {
  final message = outcome.result.firstErrorMessage;
}

How it works:

  • AsyncValidators.custom<String>() defines the async validation rule.
  • The callback can call an API, database, or service.
  • debounce delays validation so it does not run on every keystroke.
  • AsyncValidationRunner runs the rule and marks stale results so old async responses do not overwrite newer input.
  • outcome.shouldApply tells your UI whether the result still belongs to the latest validation request.

Example behavior:

  • Invalid: 'admin' returns Username is already taken
  • Valid: 'john_user' returns valid

Preview:

Async validator invalid preview Async validator valid preview

9. Error Formatting #

Use ValidationErrorFormatter when you want to convert structured validation errors into display text. This is not a field validator by itself. Validators return error keys and metadata first; the formatter turns those raw keys into messages for UI fields, summaries, logs, and localized output.

Implementation:

const formatter = ValidationErrorFormatter();

final result = ValidationResult.errors([
  const ValidationError(key: 'required'),
  const ValidationError(key: 'email'),
]);

final firstMessage = formatter.firstMessageFor(result);
final allMessages = formatter.joinedMessageFor(
  result,
  displayMode: ValidationErrorDisplayMode.all,
);

How it works:

  • ValidationError(key: 'required') stores a stable machine-readable error key.
  • ValidationErrorFormatter resolves that key into a readable message.
  • displayMode: ValidationErrorDisplayMode.all returns all errors instead of only the first one.

Example behavior:

  • Raw errors: required, email
  • Formatted messages: This field is required, Please enter a valid email address

Preview:

Error formatting raw keys preview Error formatting formatted messages preview

10. Localization #

Built-in validators return stable error keys. The formatter resolves those keys into localized messages.

By default, the formatter uses English:

const englishFormatter = ValidationErrorFormatter();

To show Spanish messages, create a formatter with localeName: 'es' and pass it into toFlutterValidator().

Implementation:

const spanishFormatter = ValidationErrorFormatter(localeName: 'es');

TextFormField(
  validator: Validators.required<String>().toFlutterValidator(
    formatter: spanishFormatter,
  ),
)

How it works:

  • Validators.required<String>() returns the error key required.
  • ValidationErrorFormatter(localeName: 'es') asks the message resolver for the Spanish message for required.
  • toFlutterValidator(formatter: spanishFormatter) makes the native Flutter field show that localized message.

Example behavior:

  • With default formatter: empty required field shows This field is required
  • With localeName: 'es': empty required field shows Este campo es obligatorio

Preview:

Localization English validation preview Localization Spanish validation preview

Custom app-specific message maps are also supported:

const resolver = MapValidationMessageResolver({
  'en': {'usernameTaken': 'Username is already taken'},
  'es': {'usernameTaken': 'El usuario ya existe'},
});

const formatter = ValidationErrorFormatter(
  localeName: 'es',
  resolver: resolver,
);

Currently bundled locales:

  • en
  • es

11. Dynamic Forms #

Use DynamicValidationForm when form fields are created at runtime. This is useful for server-driven forms, conditional fields, repeatable sections, business account fields, and feature-flagged forms.

Implementation:

final form = DynamicValidationForm()
  ..register(
    DynamicValidationField<String>(
      key: 'email',
      label: 'Email',
      validators: [Validators.required<String>(), Validators.email()],
    ),
  )
  ..register(
    DynamicValidationField<String>(
      key: 'confirmEmail',
      label: 'Confirm email',
      validators: [
        Validators.required<String>(),
        Validators.matchesField<String>('email'),
      ],
    ),
  );

form.updateValue<String>('email', 'developer@example.com');
form.updateValue<String>('confirmEmail', 'wrong@example.com');

final results = form.validateAll();
final summary = form.accessibilitySummary();

How it works:

  • register() adds a runtime field and its validators.
  • updateValue() changes a field value by key.
  • validateAll() validates every registered field.
  • Dynamic forms pass all field values into ValidationContext.values, so cross-field validators can compare values.
  • accessibilitySummary() builds readable error summary data for the current validation result.

Example behavior:

  • Email: 'developer@example.com', Confirm email: 'wrong@example.com' -> invalid
  • Email: 'developer@example.com', Confirm email: 'developer@example.com' -> valid

Preview:

Dynamic forms required validation preview Dynamic forms cross-field invalid preview Dynamic forms valid preview

12. Accessibility Summary #

Use ValidationAccessibilitySummary when you need a readable summary of form errors. This is helpful for screen-reader announcements, error summaries above a form, and focusing the first invalid field.

Implementation:

final summary = ValidationAccessibilitySummary.fromResults(
  {
    'email': ValidationResult.invalidKey('email'),
    'password': const ValidationResult.valid(),
  },
  labels: const {'email': 'Email', 'password': 'Password'},
);

print(summary.firstInvalidFieldKey);
print(summary.announcement);

How it works:

  • The summary receives validation results keyed by field name.
  • labels converts technical field keys into readable field labels.
  • firstInvalidFieldKey tells the app which field failed first.
  • announcement gives a readable sentence that can be shown or announced.

Example behavior:

  • Email invalid and password valid -> first invalid field is email
  • Announcement includes the email field label and resolved error message

Preview:

Accessibility summary field results preview Accessibility summary announcement preview

Validator Reference #

For a plain checklist of available package features, see FEATURES.md.

Core And Composition Validators #

Core validators help combine, transform, or conditionally run other validators. Use them when one field has multiple rules or when validation depends on app logic.

Available validators:

  • compose, aggregate, conditional, defaultValue, equal, notEqual
  • or, and, not, skipWhen, transform, log, withErrorMessage

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Login'),
  validator: Validators.or<String>(
    [
      Validators.email(),
      Validators.username(allowUnderscore: true),
    ],
    message: 'Enter a valid email or username',
  ).toFlutterValidator(),
)

Example behavior:

  • Invalid: 'wrong value!'
  • Valid: 'user@example.com', 'john_user'

Preview:

Core composition invalid preview Core composition username valid preview Core composition email valid preview

Basic Validators #

Basic validators cover the most common form fields: required values, email syntax, passwords, and custom patterns.

Available validators:

  • required, email, mail, password
  • pattern, regex, match, matchNot

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Work email'),
  validator: Validators.compose<String>([
    Validators.required<String>(),
    Validators.email(),
  ]).toFlutterValidator(),
)

Example behavior:

  • Invalid: '', 'usercompany.com'
  • Valid: 'user@company.com'

Preview:

Basic validators required invalid preview Basic validators email invalid preview Basic validators valid preview

Boolean And Character Validators #

Boolean validators are useful for checkbox-like values. Character validators are useful for password rules and security policies.

Available validators:

  • isTrue, isFalse
  • hasLowercaseChars, hasUppercaseChars, hasNumericChars, hasSpecialChars

Implementation:

final passwordRule = Validators.compose<String>([
  Validators.hasLowercaseChars(),
  Validators.hasUppercaseChars(),
  Validators.hasNumericChars(),
  Validators.hasSpecialChars(),
]);

Example behavior:

  • Invalid: 'password', 'Password', 'Password1'
  • Valid: 'Password1!'

Preview:

Boolean and character validators invalid preview Boolean and character validators valid preview

Collection Validators #

Collection validators are useful for dropdown selections, multi-select fields, tags, lists, and fixed-length inputs.

Available validators:

  • containsElement, equalLength, minLength, maxLength, range, unique

Implementation:

final countryRule = Validators.containsElement<String>(
  ['India', 'United States', 'Spain'],
);

Example behavior:

  • Invalid: 'Unknown'
  • Valid: 'India'

Preview:

Collection validators invalid preview Collection validators valid preview

String Validators #

String validators enforce text format rules such as casing, prefixes, suffixes, word count, and allowed characters.

Available validators:

  • alphabetical, alphanumeric, contains, endsWith
  • lowercase, uppercase, singleLine, startsWith
  • minWordsCount, maxWordsCount

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Product code'),
  validator: Validators.compose<String>([
    Validators.required<String>(),
    Validators.alphanumeric(),
    Validators.startsWith('SKU'),
  ]).toFlutterValidator(),
)

Example behavior:

  • Invalid: 'sku-123', 'ABC123'
  • Valid: 'SKU123'

Preview:

String validators invalid preview String validators valid preview

Numeric Validators #

Numeric validators check numbers, integer/decimal formats, ranges, positive/negative values, odd/even values, primes, and non-zero values.

Available validators:

  • numeric, integer, decimal, float
  • min, max, between, range
  • positiveNumber, negativeNumber, oddNumber, evenNumber
  • primeNumber, prime, notZeroNumber

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Quantity'),
  keyboardType: TextInputType.number,
  validator: Validators.compose<Object>([
    Validators.required<Object>(),
    Validators.min(1),
    Validators.max(100),
  ]).toFlutterValidator(),
)

Example behavior:

  • Invalid: '', '0', '101', 'ten'
  • Valid: '1', '25', '100'

Preview:

Numeric validators invalid preview Numeric validators valid preview

Date And Time Validators #

Date and time validators check date strings, date-time values, past/future rules, ranges, time strings, and allowed time zones.

Available validators:

  • date, dateTime, datePast, dateFuture, dateRange, time, timeZone

Implementation:

final bookingDateRule = Validators.dateRange(
  DateTime(2026, 1, 1),
  DateTime(2026, 12, 31),
);

Example behavior:

  • Invalid: '2025-12-31', '2027-01-01', 'not-a-date'
  • Valid: '2026-06-15'

Preview:

Date and time validators invalid preview Date and time validators valid preview

Network Validators #

Network validators are useful for fields that capture contact, URL, network, or location values.

Available validators:

  • url, ip, ipv4, ipv6, macAddress
  • phoneNumber, portNumber, latitude, longitude

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Support website'),
  validator: Validators.compose<String>([
    Validators.required<String>(),
    Validators.url(),
  ]).toFlutterValidator(),
)

Example behavior:

  • Invalid: 'example.com', 'ftp://example.com'
  • Valid: 'https://example.com'

Preview:

Network validators invalid preview Network validators valid preview

File Validators #

File validators help validate file-related text before upload or server submission.

Available validators:

  • fileExtension, fileName, fileSize, mimeType, path

Implementation:

final imageFileRule = Validators.compose<String>([
  Validators.required<String>(),
  Validators.fileExtension(['jpg', 'jpeg', 'png']),
]);

Example behavior:

  • Invalid: 'document.pdf', 'script.exe'
  • Valid: 'profile.png', 'photo.jpg'

Preview:

File validators invalid preview File validators valid preview

Finance And Security Validators #

Finance and security validators cover payment and banking-style form fields. These are client-side format checks only; always validate sensitive data on the server too.

Available validators:

  • creditCard, creditCardCVC, cvv, creditCardExpirationDate
  • iban, bic

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Card number'),
  keyboardType: TextInputType.number,
  validator: Validators.compose<String>([
    Validators.required<String>(),
    Validators.creditCard(),
  ]).toFlutterValidator(),
)

Example behavior:

  • Invalid: '4111 1111 1111 1112'
  • Valid: '4111 1111 1111 1111'

Preview:

Finance and security validators invalid preview Finance and security validators valid preview

Identity And Address Validators #

Identity and address validators help with profile, registration, checkout, and KYC-style forms. Some validators are intentionally practical format checks and may need server-side or country-specific validation for strict compliance.

Available validators:

  • username, firstName, lastName, city, country
  • state, street, zipCode, passport, ssn

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Username'),
  validator: Validators.compose<String>([
    Validators.required<String>(),
    Validators.username(allowUnderscore: true),
  ]).toFlutterValidator(),
)

Example behavior:

  • Invalid: 'ab', 'john user', 'john@user'
  • Valid: 'john_user', 'john123'

Preview:

Identity and address validators invalid preview Identity and address validators valid preview

Use-Case Validators #

Use-case validators cover common technical or business-specific formats.

Available validators:

  • base64, colorCode, duns, hexadecimal, isbn
  • json, languageCode, licensePlate, uuid, vin

Implementation:

TextFormField(
  decoration: const InputDecoration(labelText: 'Theme color'),
  validator: Validators.compose<String>([
    Validators.required<String>(),
    Validators.colorCode(),
  ]).toFlutterValidator(),
)

Example behavior:

  • Invalid: 'blueish', '123456'
  • Valid: '#3366FF', 'rgb(51, 102, 255)'

Preview:

Use-case validators invalid preview Use-case validators valid preview

Testing #

Run package tests:

flutter test

Run static analysis:

dart analyze

The repository keeps one Maestro regression flow for maintainer-side validation:

maestro test --platform android maestro/flows/dev-validator-regression.yaml

The current Maestro flow is:

maestro/flows/dev-validator-regression.yaml

This flow expects the local dev preview app used by maintainers. It is not required for package consumers who only import mobikul_flutter_form_validator.

Project Documentation #

Notes #

  • Client-side validation improves UX but must not replace server-side validation.
  • Empty values are valid for most format validators unless combined with Validators.required<T>().
  • The local dev preview screen is not part of the public package API.
  • Controller-based field lifecycle APIs are planned but not currently part of the public API.
  • Flutter ARB/gen-l10n integration is planned; current localization uses formatter/resolver APIs.

License #

This project is released under the MIT License. See LICENSE.

0
likes
160
points
15
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Enterprise-ready Flutter form validation foundation with planned sync, async, localized, and extensible validators.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on mobikul_flutter_form_validator