mobikul_flutter_form_validator 0.0.1
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:
Validatorsfor sync validation rules.AsyncValidatorsfor async validation rules.ValidationResultandValidationErrorfor structured validation output.ValidationErrorFormatterfor user-facing error text.ValidationMessageResolverfor localization.ValidationAccessibilitySummaryfor accessible error summaries.DynamicValidationFormfor runtime field registration.toFlutterValidator()for nativeTextFormField.validatorusage.
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.sdkconstraint. - No required form framework dependency. The package works with Flutter's native
TextFormField.
Quick Start #
- Install the package.
- Import
mobikul_flutter_form_validator. - Add a validator to
TextFormField.validator. - 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
TextFormFieldvalidation withtoFlutterValidator(). - 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:
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:
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:
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:
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:
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:
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:
passwordControllerstores 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 namedpassword.contextBuilderruns at validation time, so it always reads the latestpasswordController.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:
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.
debouncedelays validation so it does not run on every keystroke.AsyncValidationRunnerruns the rule and marks stale results so old async responses do not overwrite newer input.outcome.shouldApplytells your UI whether the result still belongs to the latest validation request.
Example behavior:
- Invalid:
'admin'returnsUsername is already taken - Valid:
'john_user'returns 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.ValidationErrorFormatterresolves that key into a readable message.displayMode: ValidationErrorDisplayMode.allreturns 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:
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 keyrequired.ValidationErrorFormatter(localeName: 'es')asks the message resolver for the Spanish message forrequired.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 showsEste campo es obligatorio
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:
enes
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:
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.
labelsconverts technical field keys into readable field labels.firstInvalidFieldKeytells the app which field failed first.announcementgives 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:
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,notEqualor,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:
Basic Validators #
Basic validators cover the most common form fields: required values, email syntax, passwords, and custom patterns.
Available validators:
required,email,mail,passwordpattern,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:
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,isFalsehasLowercaseChars,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:
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:
String Validators #
String validators enforce text format rules such as casing, prefixes, suffixes, word count, and allowed characters.
Available validators:
alphabetical,alphanumeric,contains,endsWithlowercase,uppercase,singleLine,startsWithminWordsCount,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:
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,floatmin,max,between,rangepositiveNumber,negativeNumber,oddNumber,evenNumberprimeNumber,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:
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:
Network Validators #
Network validators are useful for fields that capture contact, URL, network, or location values.
Available validators:
url,ip,ipv4,ipv6,macAddressphoneNumber,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:
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:
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,creditCardExpirationDateiban,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:
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,countrystate,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:
Use-Case Validators #
Use-case validators cover common technical or business-specific formats.
Available validators:
base64,colorCode,duns,hexadecimal,isbnjson,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:
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 #
FEATURES.md- simple list of available package features.CHANGELOG.md- version history and release notes.
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.