A generic validator with business logic separation in mind

This package provides APIs to facilitate separating validation and business rules from the application presentation.

Features

  • Express your rules in a declarative way.
  • Feedback doesn’t have to be of a certain type (totally generic)
  • Group related rules together and eliminate if else statements.
  • Supports asynchronous evalution of rules.

Usage

Check /example for a full detailed usage.

You can declare a ValidationRule with a ruler which encapsulates your validation and of course the negativeFeedback of your choice which can be of any type then call apply on it.

final myName = 'Ahmed';
final arabicName = 'احمد';
final nameRule = ValidationRule(
            ruler: (value) {
              if (RegExp("[A-Za-z]").hasMatch(value)) {
                return true;
              }
              return false;
            },
            negativeFeedback: "Name has to be in English");
final validResult = nameRule.apply(myName); // Returns Valid
final invalidResult = nameRule.apply(arabicName); // Returns Invalid which has a reasons property of the type of the negative feedback passed earlier.

Grouping rules together can be convenient in that case, you can subclass the Validator and override its rules getter declaring your rules.

class NameValidator extends Validator<String, dynamic> {
  @override
  List<ValidationRule<String, dynamic>> get rules => [
      ValidationRule(
            ruler: (value) {
              if (value.isNotEmpty){
                return true;
              }
              return false;
            },
            negativeFeedback: "Name can't be empty"),
        ValidationRule(
            ruler: (value) {
              if (RegExp("[A-Za-z]").hasMatch(value)) {
                return true;
              }
              return false;
            },
            negativeFeedback: "Name has to be in English"),
      ];
}

Call validate on it to get the result:

  
  final aValidName = "Ahmed";
  final invalidName = "";
  final nameValidator = NameValidator();
  final validResult = nameValidator.validate(aValidName); // Returns Valid
  final invalidResult = nameValidator.validate(invalidName); // Returns Invalid with a reasons property

You can also mixin StringRules which has common rules like notEmpty

class NameValidator extends Validator<String, dynamic> with StringRules {
  @override
  List<ValidationRule<String, dynamic>> get rules => [
        notEmpty<String>(negativeFeedback: "This field can't be empty."),
        max(
            maxLength: 10,
            negativeFeedback: "Name can't be more than 10 characters."),
        ValidationRule(
            ruler: (value) {
              if (RegExp("[A-Za-z]").hasMatch(value)) {
                return true;
              }
              return false;
            },
            negativeFeedback: "Name has to be in English"),
      ];
}

You can create both an AsyncValidationRule and an AsyncValidator which can have both ValidationRule and AsyncValidationRules rules.

A validation result have a couple of convient methods like when, maybeWhen, map, and maybeMap. for usages like:

// Map validation result objects to String or null for use with the TextFormfield validator function.
nameValidator.validate(val).maybeMap(
    invalid: (invalid) => invalid.reasons.first,
    orElse: () => null,
);
// Side effects based on a validation result.
phoneValidator.validate(val).maybeWhen(
      invalid: (reasons) {
        // show a dialog
      },
      orElse: () {
        // pop the current screen
      },
);

GitHub

View Github