form_shield 0.3.0 copy "form_shield: ^0.3.0" to clipboard
form_shield: ^0.3.0 copied to clipboard

A declarative, rule-based form validation library for Flutter apps. Supports async validation, custom validation logic, and more.

Form Shield #

Form Shield Logo

pub version license codecov

A declarative, rule-based form validation library for Flutter apps, offering customizable rules and messages, seamless integration with Flutter forms, type safety, and chainable validation.

It provides a simple yet powerful way to define and apply validation logic to your form fields.

Features #

Declarative Validation: Define validation rules in a clear, readable way.
🎨 Customizable: Easily tailor rules and error messages to your needs.
🤝 Flutter Integration: Works seamlessly with Flutter's Form and TextFormField widgets.
🔒 Type-Safe: Leverages Dart's type system for safer validation logic.
🔗 Chainable Rules: Combine multiple validation rules effortlessly.
📚 Comprehensive Built-in Rules: Includes common validation scenarios out-of-the-box (required, email, password, length, numeric range, phone, etc.).
🛠️ Extensible: Create your own custom validation rules by extending the base class.

Table of Contents #

Getting started #

Installation #

Add form_shield to your pubspec.yaml dependencies:

dependencies:
  flutter:
    sdk: flutter
  form_shield: ^0.3.0
copied to clipboard

Then, run flutter pub get.

Usage #

Basic usage #

Import the package:

import 'package:form_shield/form_shield.dart';
copied to clipboard

Wrap your TextFormField (or other form fields) within a Form widget and assign a GlobalKey<FormState>. Use the Validator class to attach rules to the validator property of your fields:

import 'package:flutter/material.dart';
import 'package:form_shield/form_shield.dart';

class MyForm extends StatelessWidget {
  final _formKey = GlobalKey<FormState>();
  
  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            decoration: InputDecoration(labelText: 'Email'),
            validator: Validator<String>([
              RequiredRule(),
              EmailRule(),
            ]),
          ),
          TextFormField(
            decoration: InputDecoration(labelText: 'Password'),
            obscureText: true,
            validator: Validator<String>([
              RequiredRule(),
              PasswordRule(),
            ]),
          ),
          ElevatedButton(
            onPressed: () {
              if (_formKey.currentState!.validate()) {
                // Form is valid, proceed
              }
            },
            child: Text('Submit'),
          ),
        ],
      ),
    );
  }
}
copied to clipboard

Customizing error messages #

Validator<String>([
  RequiredRule(errorMessage: 'Please enter your email address'),
  EmailRule(errorMessage: 'Please enter a valid email address'),
])
copied to clipboard

Using multiple validation rules #

Validator<String>([
  RequiredRule(),
  MinLengthRule(8, errorMessage: 'Username must be at least 8 characters'),
  MaxLengthRule(20, errorMessage: 'Username cannot exceed 20 characters'),
])
copied to clipboard

Custom validation rules #

Validator<String>([
  RequiredRule(),
  CustomRule(
    validator: (value) => value != 'admin',
    errorMessage: 'Username cannot be "admin"',
  ),
])
copied to clipboard

Dynamic custom validation #

Validator<String>([
  DynamicCustomRule(
    validator: (value) {
      if (value == null || value.isEmpty) {
        return 'Please enter a value';
      }
      if (value.contains(' ')) {
        return 'No spaces allowed';
      }
      return null; // Validation passed
    },
  ),
])
copied to clipboard

Validating numbers #

Validator<num>([
  MinValueRule(18, errorMessage: 'You must be at least 18 years old'),
  MaxValueRule(120, errorMessage: 'Please enter a valid age'),
])
copied to clipboard

Phone number validation #

// General phone validation
Validator<String>([
  RequiredRule(),
  PhoneRule(),
])

// Country-specific phone validation
Validator<String>([
  RequiredRule(),
  CountryPhoneRule(countryCode: 'CM'),
])
copied to clipboard

Password validation with options #

Validator<String>([
  RequiredRule(),
  PasswordRule(
    options: PasswordOptions(
      minLength: 10,
      requireUppercase: true,
      requireLowercase: true,
      requireDigit: true,
      requireSpecialChar: true,
    ),
    errorMessage: 'Password does not meet security requirements',
  ),
])
copied to clipboard

Password confirmation #

final passwordController = TextEditingController();

// Password field
TextFormField(
  controller: passwordController,
  validator: Validator<String>([
    RequiredRule(),
    PasswordRule(),
  ]),
)

// Confirm password field
TextFormField(
  validator: Validator<String>([
    RequiredRule(),
    PasswordMatchRule(
      passwordGetter: () => passwordController.text,
      errorMessage: 'Passwords do not match',
    ),
  ]),
)
copied to clipboard

Available validation rules #

  • RequiredRule - Validates that a value is not null or empty
  • EmailRule - Validates that a string is a valid email address
  • PasswordRule - Validates that a string meets password requirements
  • PasswordMatchRule - Validates that a string matches another string
  • LengthRule - Validates that a string's length is within specified bounds
  • MinLengthRule - Validates that a string's length is at least a specified minimum
  • MaxLengthRule - Validates that a string's length is at most a specified maximum
  • ValueRule - Validates that a numeric value is within specified bounds
  • MinValueRule - Validates that a numeric value is at least a specified minimum
  • MaxValueRule - Validates that a numeric value is at most a specified maximum
  • PhoneRule - Validates that a string is a valid phone number
  • CountryPhoneRule - Validates that a string is a valid phone number for a specific country
  • UrlRule - Validates that a string is a valid URL
  • IPAddressRule - Validates that a string is a valid IPv4 or IPv6 address
  • CreditCardRule - Validates that a string is a valid credit card number
  • DateRangeRule - Validates that a date is within a specified range
  • CustomRule - A validation rule that uses a custom function to validate values
  • DynamicCustomRule - A validation rule that uses a custom function to validate values and return a dynamic error message

Creating your own validation rules #

You can create your own validation rules by extending the ValidationRule class:

class NoSpacesRule extends ValidationRule<String> {
  const NoSpacesRule({
    String errorMessage = 'No spaces allowed',
  }) : super(errorMessage: errorMessage);

  @override
  ValidationResult validate(String? value) {
    if (value == null || value.isEmpty) {
      return const ValidationResult.success();
    }

    if (value.contains(' ')) {
      return ValidationResult.error(errorMessage);
    }

    return const ValidationResult.success();
  }
}
copied to clipboard

Then use it like any other validation rule:

Validator<String>([
  RequiredRule(),
  NoSpacesRule(),
])
copied to clipboard

Asynchronous validation rules #

Form Shield supports asynchronous validation for scenarios where validation requires network requests or other async operations (like checking username availability or email uniqueness).

You can create async validation rules by either:

  1. Extending the ValidationRule class and overriding the validateAsync method
  2. Extending the specialized AsyncValidationRule class

Example: Username availability checker

class UsernameAvailabilityRule extends ValidationRule<String> {
  final Future<bool> Function(String username) _checkAvailability;

  const UsernameAvailabilityRule({
    required Future<bool> Function(String username) checkAvailability,
    super.errorMessage = 'This username is already taken',
  }) : _checkAvailability = checkAvailability;

  @override
  ValidationResult validate(String? value) {
    // Perform synchronous validation first
    if (value == null || value.isEmpty) {
      return ValidationResult.error('Username cannot be empty');
    }
    return const ValidationResult.success();
  }

  @override
  Future<ValidationResult> validateAsync(String? value) async {
    // Run sync validation first
    final syncResult = validate(value);
    if (!syncResult.isValid) {
      return syncResult;
    }

    try {
      // Perform the async validation
      final isAvailable = await _checkAvailability(value!);
      if (isAvailable) {
        return const ValidationResult.success();
      } else {
        return ValidationResult.error(errorMessage);
      }
    } catch (e) {
      return ValidationResult.error('Error checking username availability: $e');
    }
  }
}
copied to clipboard

Using async validation in forms

When using async validation, you need to:

  1. Create a validator instance as a field in your state class
  2. Initialize it in initState()
  3. Dispose of it in dispose()
  4. Check both sync and async validation states before submitting
class _MyFormState extends State<MyForm> {
  final _formKey = GlobalKey<FormState>();
  final _usernameController = TextEditingController();
  late final Validator<String> _usernameValidator;

  @override
  void initState() {
    super.initState();

    _usernameValidator = Validator<String>([
      RequiredRule(),
      UsernameAvailabilityRule(
        checkAvailability: _checkUsernameAvailability,
      ),
    ], debounceDuration: Duration(milliseconds: 500));
  }

  @override
  void dispose() {
    _usernameController.dispose();
    _usernameValidator.dispose(); // Important to prevent memory leaks
    super.dispose();
  }

  Future<bool> _checkUsernameAvailability(String username) async {
    // Simulate API call with delay
    await Future.delayed(const Duration(seconds: 1));
    final takenUsernames = ['admin', 'user', 'test'];
    return !takenUsernames.contains(username.toLowerCase());
  }

  void _submitForm() {
    if (_formKey.currentState!.validate() &&
        !_usernameValidator.isValidating &&
        _usernameValidator.isValid) {
      // All validations passed, proceed with form submission
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _usernameController,
            decoration: InputDecoration(labelText: 'Username'),
            validator: _usernameValidator,
          ),
          // Show async validation state
          ValueListenableBuilder<AsyncValidationStateData>(
            valueListenable: _usernameValidator.asyncState,
            builder: (context, state, _) {
              if (state.isValidating) {
                return Text('Checking username availability...');
              } else if (state.isValid == false) {
                return Text(
                  state.errorMessage ?? 'Invalid username',
                  style: TextStyle(color: Colors.red),
                );
              } else if (state.isValid == true) {
                return Text(
                  'Username is available',
                  style: TextStyle(color: Colors.green),
                );
              }
              return SizedBox.shrink();
            },
          ),
          ElevatedButton(
            onPressed: _submitForm,
            child: Text('Submit'),
          ),
        ],
      ),
    );
  }
}
copied to clipboard

Debouncing async validation

Form Shield includes built-in debouncing for async validation to prevent excessive API calls during typing. You can customize the debounce duration:

Validator<String>([
  RequiredRule(),
  UsernameAvailabilityRule(checkAvailability: _checkUsername),
], debounceDuration: Duration(milliseconds: 800)) // Custom debounce time
copied to clipboard

Manually triggering async validation

You can manually trigger async validation using the validateAsync method:

Future<void> _checkUsername() async {
  final isValid = await _usernameValidator.validateAsync(
    _usernameController.text,
    debounceDuration: Duration.zero, // Optional: skip debouncing
  );
  
  if (isValid) {
    // Username is valid and available
  }
}
copied to clipboard

Contributing #

Contributions are welcome! Please feel free to submit issues, pull requests, or suggest improvements.

License #

This project is licensed under the MIT License - see the LICENSE file for details.

12
likes
160
points
12
downloads

Publisher

verified publisherstevenosse.com

Weekly Downloads

2024.09.19 - 2025.04.03

A declarative, rule-based form validation library for Flutter apps. Supports async validation, custom validation logic, and more.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on form_shield