Rules - Powerful and feature-rich validation library for both Dart and Flutter.

Overview

Introduction

Rules - Powerful and feature-rich validation library for both Dart and Flutter.

Rules is a simple yet powerful and feature-rich validation library for both dart and flutter.

Features

  • Highly flexible
  • Easy to understand
  • Less boilerplate code
  • Custom error handling
  • Override individual errors
  • Flutter friendly
  • State management libraries friendly (Mobx example included)

Installation

Go to https://pub.dev/packages/rules#-installing-tab- for the latest version of rules

To activate pre-commit hooks

$ git config core.hooksPath .githooks/
$ chmod +x .githooks/pre-commit
$ chmod +x .githooks/pre-push

Concept

The Rules library has three parts

  • Rule: Basic rule
  • GroupRule: Group together and validate the basic rules
  • CombinedRule: Validate multiple basic rules and group rules together

Usage

Import the library

import 'package:rules/rules.dart';

1. Rule (Basic Rule)

This is the basic building block, everything starts here.

Basic example

void main() {
  const textFieldValue = '[email protected]';

  final rule = Rule(
    textFieldValue, // value; string and null are accepted
    name: 'Text field', // placeholder value which will be used while displaying errors
  );

  print(rule.error);
  // output: null
  print(rule.hasError);
  // output: false
}

Example 1

void main() {
  const textFieldValue = ''; // or const textFieldValue = null;

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isRequired: true,
  );

  print(rule.error);
  // output: 'Text field is required'
  print(rule.hasError);
  // output: true

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}

Example 2

void main() {
  const textFieldValue = 'abc@xyz';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isRequired: true,
    isEmail: true,
  );

  print(rule.error);
  // output: 'Text field is not a valid email address'
  print(rule.hasError);
  // output: true

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}

Available rules

String name; // mandatory

bool isRequired;

bool isEmail;

bool isUrl;

bool isPhone;

bool isIp;

bool isNumeric;

bool isNumericDecimal;

bool isAlphaSpace;

bool isAlphaNumeric;

bool isAlphaNumericSpace;

RegExp regex;

int length;

int minLength;

int maxLength;

double greaterThan;

double greaterThanEqualTo;

double lessThan;

double lessThanEqualTo;

double equalTo;

double notEqualTo;

List<double> equalToInList;

List<double> notEqualToInList;

String shouldMatch;

String shouldNotMatch;

bool shouldPass(String value);

List<String> inList;

List<String> notInList;

Available configurations

String customErrorText;

Map<String, String> customErrors;

RuleOptions options;
isRequired: bool
void main() {
  const textFieldValue = '123';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isRequired: true, // throws an error for value = null or an empty string
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
  • IMPORTANT: If the value is empty or null and 'isRequired' is false or not set then no errors will be thrown for the subsequent constraints.
void main() {
  const textFieldValue = ''; // or null

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isRequired: false,
    isEmail: true,
  );

  print(rule.hasError);
  // output: false
}
isEmail: bool
void main() {
  const textFieldValue = '[email protected]';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isEmail: true,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
isUrl: bool
void main() {
  const textFieldValue = 'http://www.google.com';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isUrl: true,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
isPhone: bool
  • It recognizes the phone numbers starting with + or 0, no length limitations and handles #, x, ext, extension extension conventions.
void main() {
  const textFieldValue = '+1-9090909090';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isPhone: true,
  );

  if (rule.hasError) {
  // some action on error
  } else {
  // Some action on success
  }
}
isIp: bool
  • It accepts both IPv4 and IPv6 addresses.
void main() {
  const textFieldValue = '1.1.1.1';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isIp: true,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
isNumeric: bool
  • It accepts both 0, positive and negative integers. Decimals are not allowed.
void main() {
  const textFieldValue = '1'; // '-1', '0'

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isNumeric: true,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
isNumericDecimal: bool
  • It accepts 0, postive and negative integers and decimals numbers.
void main() {
  const textFieldValue = '10.01'; // '-10.01' or '0.001'

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isNumericDecimal: true,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
isAlphaSpace: bool
  • It accepts multiple spaces and alphabets (both upper and lower case).
void main() {
  const textFieldValue = 'Jane Doe';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isAlphaSpace: true,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
isAlphaNumeric: bool
  • It accepts alphabets (both upper and lower case) and numbers.
void main() {
  const textFieldValue = 'username123';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isAlphaNumeric: true,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
isAlphaNumericSpace: bool
  • It accepts multiple spaces, alphabets (both upper and lower case) and numbers.
void main() {
  const textFieldValue = 'Bread 20';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isAlphaNumericSpace: true,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
regex: RegExp
  • It accepts a custom regular expression string.
void main() {
  const textFieldValue = 'abc123';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    regex: RegExp(
      r'^[a-zA-Z0-9\s]+$',
      caseSensitive: false,
    ),
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
length: int
  • Defines the length of the input string.
void main() {
  const textFieldValue = '9090909090';

  final rule = Rule(
    textFieldValue,
    name: 'Phone Number',
    length: 10,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
minLength: int
  • Defines the minimum length of the input string.
void main() {
  const textFieldValue = 'abcd12345';

  final rule = Rule(
    textFieldValue,
    name: 'Password',
    minLength: 6,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
maxLength: int
  • Defines the maximum length of the input string.
void main() {
  const textFieldValue = 'username12';

  final rule = Rule(
    textFieldValue,
    name: 'Username',
    minLength: 10,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
greaterThan: double
  • Checks if the input value is greater than the 'greaterThan' value.
  • if 'isNumeric' is not set then the 'isNumericDecimal' constraint will be applied automatically.
void main() {
  const textFieldValue = '10';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    greaterThan: 0,
    isNumeric: true, // optional
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
greaterThanEqualTo: double
  • Checks if the input value is greater than or equal to the 'greaterThanEqualTo' value.
  • if 'isNumeric' is not set then 'isNumericDecimal' constraint will be applied automatically.
void main() {
  const textFieldValue = '5.1';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    greaterThanEqualTo: 5.0,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
lessThan: double
  • Checks if the input value is greater than or equal to the 'lessThan' value.
  • if 'isNumeric' is not set then 'isNumericDecimal' constraint will be applied automatically.
void main() {
  const textFieldValue = '1';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    lessThan: 2.0,
    isNumericDecimal: true, // optional
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
lessThanEqualTo: double
  • Checks if the input value is greater than or equal to the 'lessThanEqualTo' value.
  • if 'isNumeric' is not set then 'isNumericDecimal' constraint will be applied automatically.
void main() {
  const textFieldValue = '2.0';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    lessThanEqualTo: 2.0,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
equalTo: double
  • Checks if the input value is equal to the 'equalTo' value.
  • if 'isNumeric' is not set then 'isNumericDecimal' constraint will be applied automatically.
void main() {
  const textFieldValue = '2.0';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    equalTo: 2.0,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
notEqualTo: double
  • Checks if the input value is equal to the 'notEqualTo' value.
  • It will throw an error if the values match.
  • if 'isNumeric' is not set then 'isNumericDecimal' constraint will be applied automatically.
void main() {
  const textFieldValue = '2.0';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    notEqualTo: 2.0,
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
equalToInList: List<double>
  • Checks if the input value matches with any of the 'equalToInList' values.
  • Note: This is a float comparison. 10.00 == 10 will be true.
void main() {
  const textFieldValue = '10';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    equalToInList: [0, 10],
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
notEqualToInList: List<double>
  • Checks if the input value matches with any of the 'notEqualToInList' values.
  • It will throw an error if there is a value match.
  • Note: This is a float comparison. 10.00 == 10 will be true.
void main() {
  const textFieldValue = '10';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    notEqualToInList: [0, 10],
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
shouldMatch: String
  • Checks if the input value matches with the 'shouldMatch' value.
  • Note: This is a string comparison.
void main() {
  const textFieldValue = 'abc';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    shouldMatch: 'abc',
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
shouldNotMatch: String
  • Checks if the input value matches with the 'shouldNotMatch' value.
  • It will throw an error if the values match.
  • Note: This is a string comparison.
void main() {
  const textFieldValue = 'abc';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    shouldNotMatch: 'xyz',
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
shouldPass: bool Function(String value)
  • Checks if the input value passes the given function check
  • It will throw an error if the function returns false
void main() {
  const textFieldValue = 'abc';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    shouldPass: (value) => value.contains('a') && value.contains('b')
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
inList: List<String>
  • Checks if the input value matches with any of the 'inList' values.
  • Note: This is a string comparison.
void main() {
  const textFieldValue = 'abc';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    inList: ['abc', 'xyz'],
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
notInList: List<String>
  • Checks if the input value matches with any of the 'notInList' values.
  • It will throw an error if there is a value match.
  • Note: This is a string comparison.
void main() {
  const textFieldValue = 'abc';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    notInList: ['abc', 'xyz'],
  );

  if (rule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}

Custom Error

Default errors
'isRequired': '{name} is required'
'isEmail': '{name} is not a valid email address'
'isPhone': '{name} is not a valid phone number'
'isUrl': '{name} is not a valid URL'
'isIp': '{name} is not a valid IP address'
'isNumeric': '{name} is not a valid number'
'isNumericDecimal': '{name} is not a valid decimal number'
'isAlphaSpace': 'Only alphabets and spaces are allowed in {name}'
'isAlphaNumeric': 'Only alphabets and numbers are allowed in {name}'
'isAlphaNumericSpace': 'Only alphabets, numbers and spaces are allowed in {name}'
'regex': '{name} should match the pattern: {regex.pattern}'
'length': '{name} should be $length characters long'
'minLength': '{name} should contain at least $minLength characters'
'maxLength': '{name} should not exceed more than $maxLength characters'
'greaterThan': '{name} should be greater than $greaterThan'
'greaterThanEqualTo': '{name} should be greater than or equal to $greaterThanEqualTo'
'lessThan': '{name} should be less than $lessThan'
'lessThanEqualTo': '{name} should be less than or equal to $lessThanEqualTo'
'equalTo': '{name} should be equal to $equalTo'
'notEqualTo': '{name} should not be equal to $notEqualTo'
'equalToInList': '{name} should be equal to any of these values $equalToInList'
'notEqualToInList': '{name} should not be equal to any of these values $notEqualToInList'
'shouldMatch': '{name} should be same as $shouldMatch'
'shouldNotMatch': '{name} should not same as $shouldNotMatch'
'shouldPass': '{name} is invalid'
'inList': '{name} should be any of these values $inList'
'notInList': '{name} should not be any of these values $notInList'
Override the default errors
  • To override the error text of a particular option, set 'customErrors' as {'optionName': '<Error Text>'}.
  • The 'optionName' key should match with one of the 'Available rules' for overriding the error text.
  • Use {value} and {name} template variables in the 'customErrors' to display the input name and value respectively.
  • To override all default error texts set 'customErrorText'.
  • Note: 'customErrorText' will only override the default errors. 'customErrors' will be given the highest priority.
void main() {
  const textFieldValue = ''; // or textFieldValue = 'xyz';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isRequired: true,
    isEmail: true,
    customErrors: {
      'isRequired': 'Input is invalid.',
      'isEmail': '{value} is an invalid value. Try another {name}.',
    },
  );

  // when textFieldValue = '';
  print(rule.error);
  // output: Input is invalid.

  // when textFieldValue = 'xyz';
  print(rule.error);
  // output: xyz is an invalid value. Try another Text field.
}
void main() {
  const textFieldValue = '123';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
    isRequired: true,
    isEmail: true,
    customErrorText: 'Invalid email address',
  );

  print(rule.error);
  // output: Invalid email address
}

Extension

  • Extend and override the constraints of a rule using 'copyWith' method
void main() {
  const textFieldValue = 'abc';

  final rule1 = Rule(
    textFieldValue,
    name: 'Text field 1',
    isAlpha: true,
    customErrorText: 'Only alphabets allowed',
  );

  final rule2 = rule1.copyWith(
    name: 'Text field 2',
    isEmail: true,
    customErrorText: 'Invalid email address',
  );

  print(rule1.error);
  // output: null
  print(rule2.error);
  // output: Invalid email address
}
  • The child rule will default to the constraint values of the parent unless they are set explicitly in the child.
void main() {
  const textFieldValue = '';

  // parent rule
  final rule1 = Rule(
    textFieldValue,
    name: 'Text field',
    isRequired: true,
    customErrorText: 'Invalid input',
  );

  // child rule
  final rule2 = rule1.copyWith(
    isRequired: false,
  );

  print(rule1.error);
  // output: Invalid input
  print(rule2.error);
  // output: null
}

2. GroupRule

Group together and validate the basic rules

Basic example

void main() {
  const textFieldValue = '[email protected]';

  final rule = Rule(
    textFieldValue,
    name: 'Text field',
  );

  final groupRule = GroupRule(
    [rule], // value; List of Rule
    name: 'Group name', // placeholder value which will be used while displaying errors
  );

  print(groupRule.error);
  // output: null
  print(groupRule.hasError);
  // output: false

  if (groupRule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}

Example 1

void main() {
  const textFieldValue1 = ''; // or const textFieldValue = null;
  const textFieldValue2 = '';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field 1',
    isRequired: true,
  );

  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field 2',
    isEmail: true,
  );

  final groupRule = GroupRule(
    [rule1, rule2], // value; List of Rule
    name: 'Group name', // placeholder value which will be used while displaying errors
  );

  print(groupRule.error);
  // output: 'Text field 1 is required'
  print(groupRule.hasError);
  // output: true
}

Available rules

String name; // mandatory

bool requiredAll;

int requiredAtleast;

int maxAllowed;

Available configurations

String customErrorText;

Map<String, String> customErrors;
requiredAll: bool
  • Checks if all basic rules have a value.
void main() {
  const textFieldValue1 = 'abc';
  const textFieldValue2 = '';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field',
    isRequired: true,
  ); // Validation OK

  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field',
  ); // Validation OK

  final groupRule = GroupRule(
    [rule1, rule2],
    name: 'Group name',
    requiredAll: true,
  );

  print(groupRule.error);
  // output: All fields are mandatory in Group name
}
  • IMPORTANT: If any of the basic rules have validation errors then GroupRule will throw those errors first.
  • The group validation wouldn't happen until all basic rules pass the validation.
void main() {
  const textFieldValue1 = 'abc'; // or const textFieldValue = null;
  const textFieldValue2 = 'xyz@abc';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field 1',
    isRequired: true,
  ); // Validation OK

  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field 2',
    isEmail: true,
  ); // Validation FAILED

  final groupRule = GroupRule([rule1, rule2], name: 'Group name');

  print(groupRule.error);
  // output: 'Text field 2 is not a valid email address'
  print(groupRule.hasError);
  // output: true

  if (groupRule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}
  • IMPORTANT: If the input basic rules list is an empty array or null and 'isRequiredAll' is false or not set then no errors will be thrown for the subsequent constraints.
void main() {
  final groupRule = GroupRule([], name: 'Group name');

  print(groupRule.hasError);
  // output: false
}
requiredAtleast: int
  • Defines the minimum number of basic rules that should have a value.
  • The number of basic rules in a GroupRule should be greater than the 'requiredAtleast' value else it will throw an exception.
  • If the 'requiredAtleast' is 0 then it will pass the validation.
void main() {
  const textFieldValue1 = 'abc';
  const textFieldValue2 = '';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field',
  ); // Validation OK

  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field',
  ); // Validation OK

  final groupRule = GroupRule(
    [rule1, rule2],
    name: 'Group name',
    requiredAtleast: 2,
  );

  print(groupRule.error);
  // output: At least 2 fields are required in Group name
}
void main() {
  const textFieldValue1 = 'abc';
  const textFieldValue2 = 'xyz';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field',
  ); // Validation OK

  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field',
  ); // Validation OK

  final groupRule = GroupRule(
    [rule1, rule2],
    name: 'Group name',
    requiredAtleast: 2,
  );

  print(groupRule.error);
  // output: null

  print(groupRule.hasError);
  // output: false
}
maxAllowed: int
  • The maximum number of basic rules that are allowed to have a value.
void main() {
  const textFieldValue1 = 'abc';
  const textFieldValue2 = 'xyz';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field',
  ); // Validation OK

  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field',
  ); // Validation OK

  final groupRule = GroupRule(
    [rule1, rule2],
    name: 'Group name',
    maxAllowed: 1,
  );

  print(groupRule.error);
  // output: A maximum of 1 field is allowed in Group name
}
void main() {
  const textFieldValue1 = 'abc';
  const textFieldValue2 = '';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field',
  ); // Validation OK

  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field',
  ); // Validation OK

  final groupRule = GroupRule(
    [rule1, rule2],
    name: 'Group name',
    maxAllowed: 1,
  );

  print(groupRule.error);
  // output: null

  print(groupRule.hasError);
  // output: false
}

Custom Error

Default errors
  • Plurality for 'requiredAtleast' and 'maxAllowed' error texts will be automatically detected.
'requiredAll': 'All fields are mandatory in {name}'
'requiredAtleast': 'At least $requiredAtleast field(s) is/are required in {name}'
'maxAllowed': 'A maximum of $maxAllowed field(s) is/are allowed in {name}'
Override the default errors
  • To override the error text of a particular option, set 'customErrors' as {'optionName': '<Error Text>'}.
  • The 'optionName' key should match with one of the 'Available rules' for overriding the error text.
  • Use {value} and {name} template variables in the 'customErrors' to display the input name and value respectively.
  • To override all default error texts set 'customErrorText'.
  • Note: 'customErrorText' will only override the default errors. 'customErrors' will be given the highest priority.
void main() {
  const textFieldValue1 = 'abc';
  const textFieldValue2 = '';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field',
    isRequired: true,
  ); // Validation OK

  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field',
  ); // Validation OK

  final groupRule = GroupRule(
    [rule1, rule2],
    name: 'Group name',
    requiredAll: true,
    customErrors: {
      'requiredAll': 'Input is invalid.',
    },
  );

  print(groupRule.error);
  // output: Input is invalid.
}
void main() {
  const textFieldValue1 = 'abc';
  const textFieldValue2 = '';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field',
    isRequired: true,
  ); // Validation OK

  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field',
  ); // Validation OK

  final groupRule = GroupRule(
    [rule1, rule2],
    name: 'Group name',
    requiredAll: true,
    customErrorText: 'Invalid input.',
  );

  print(groupRule.error);
  // output: Invalid input.
}

Options

Available options
bool trim;

bool lowerCase;

bool upperCase;
trim: bool
  • Removes any leading and trailing whitespace from the input value
  • Use rule.value for the trimmed value
import 'package:rules/src/models/rule_options.dart';

void main() {
  final rule1 = Rule(
    '    ',
    name: 'Name',
    isRequired: true,
    options: RuleOptions(
      trim: true,
    ),
  );

  print(rule1.hasError);
  // output: true

  print(rule1.value);
  // output: 
  
  final rule2 = Rule(
    '     [email protected]     ',
    name: 'Email',
    isEmail: true,
    options: RuleOptions(
      trim: true,
    ),
  );

  print(rule2.hasError);
  // output: false
  
  print(rule2.value);
  // output: [email protected]
}
lowercase: bool
  • Converts all characters, in the input string, to lower case
  • Use rule.value for the converted value
import 'package:rules/src/models/rule_options.dart';

void main() {
  final rule = Rule(
    'ABC',
    name: 'Input field',
    regex: RegExp('[a-z]'),
    options: RuleOptions(
      lowerCase: true,
    ),
  );

  print(rule.hasError);
  // output: false

  print(rule.value);
  // output: abc
}
upperCase: bool
  • Converts all characters, in the input string, to upper case
  • Use rule.value for the converted value
import 'package:rules/src/models/rule_options.dart';

void main() {
  final rule = Rule(
    'xyz',
    name: 'Input field',
    regex: RegExp('[A-Z]'),
    options: RuleOptions(
      upperCase: true,
    ),
  );

  print(rule.hasError);
  // output: false

  print(rule.value);
  // output: xyz
}

Extension

  • Extend and override the constraints of a group rule using 'copyWith' method.
  • The child rule will default to the constraint values of the parent unless they are set explicitly in the child.
void main() {
  const textFieldValue1 = 'abc';
  const textFieldValue2 = '';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field',
    isRequired: true,
  ); // Validation OK

  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field',
  ); // Validation OK

  // parent
  final groupRule1 = GroupRule(
    [rule1, rule2],
    name: 'Group name 1',
    requiredAll: true,
    customErrorText: 'Invalid input for Group 1',
  ); // Validation FAILED

  // child
  final groupRule2 = groupRule1.copyWith(
    name: 'Group name 2',
    requiredAll: false,
    customErrorText: 'Invalid input for Group 2',
  ); // Validation OK

  print(groupRule1.error);
  // output: Invalid input for Group 1

  print(groupRule2.error);
  // output: null
}
void main() {
  const textFieldValue1 = 'abc';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field',
    isRequired: true,
  ); // Validation OK

  final rule2 = rule1.copyWith(
    name: 'Text field',
  ); // Validation OK

  // parent
  final groupRule1 = GroupRule(
    [rule1, rule2],
    name: 'Group name 1',
    requiredAll: true,
    customErrorText: 'Invalid input for Group 1',
  ); // Validation OK

  // child
  final groupRule2 = groupRule1.copyWith(
    name: 'Group name 2',
    requiredAll: false,
    customErrorText: 'Invalid input for Group 2',
  ); // Validation OK

  print(groupRule1.error);
  // output: null

  print(groupRule2.error);
  // output: null
}

3. CombinedRule

Manage multiple Rules and GroupRules

  • Both 'Rule' and/or 'GroupRule' are accepted as inputs.
  • Errors of both 'Rule' and 'GroupRule', if any, are combined into a list.
  • Order of appearance of error texts in CombinedRule errorList:
    1. Rule
    2. GroupRule

Basic example

void main() {
  const textFieldValue1 = '';
  const textFieldValue2 = '[email protected]';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field 1',
  ); // Validation OK
  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field 2',
    isEmail: true,
  ); // Validation OK
  final groupRule = GroupRule([rule1, rule2],
      name: 'Group name', requiredAll: true); // Validation FAILED

  const textFieldValue3 = '';
  final rule3 = Rule(
    textFieldValue3,
    name: 'Text field 3',
    isRequired: true,
  ); // Validation FAILED

  final combinedRule = CombinedRule(
    rules: [rule3],
    groupRules: [groupRule],
  );

  print(combinedRule.errorList);
  // output: ['Text field 3 is required', 'All fields are mandatory in Group name']

  print(combinedRule.hasError);
  // output: true

  if (combinedRule.hasError) {
    // some action on error
  } else {
    // Some action on success
  }
}

Available fields

List<Rule> rules;

List<GroupRule> groupRules;
rules: List<Rule>
void main() {
  const textFieldValue1 = '';
  const textFieldValue2 = 'abc@xyz';
  const textFieldValue3 = '';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field 1',
  ); // Validation OK
  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field 2',
    isEmail: true,
  ); // Validation FAILED
  final rule3 = Rule(
    textFieldValue3,
    name: 'Text field 3',
    isRequired: true,
    customErrorText: 'Invalid field input',
  ); // Validation FAILED

  final combinedRule = CombinedRule(
    rules: [rule1, rule2, rule3],
  );

  print(combinedRule.errorList);
  // output: ['Text field 2 is not a valid email address', 'Invalid field input']

  print(combinedRule.hasError);
  // output: true
}
groupRules: List<GroupRule>
void main() {
  const textFieldValue1 = '';
  const textFieldValue2 = 'abc@xyz';

  final rule1 = Rule(
    textFieldValue1,
    name: 'Text field 1',
  ); // Validation OK
  final rule2 = Rule(
    textFieldValue2,
    name: 'Text field 2',
    isEmail: true,
  ); // Validation FAILED
  final groupRule1 = GroupRule(
    [rule1, rule2],
    name: 'Group name',
    requiredAll: true,
    customErrorText: 'Invalid input',
  ); // Validation FAILED

  final groupRule2 = GroupRule(
    [],
    name: 'Group name',
  ); // Validation OK

  final combinedRule = CombinedRule(
    groupRules: [groupRule1, groupRule2],
  );

  print(combinedRule.errorList);
  // output: ['Invalid input']

  print(combinedRule.hasError);
  // output: true
}

Flutter

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  String emailInput;

  String phoneInput;

  void _handleEmailTextFieldOnChange(String value) {
    setState(() {
      emailInput = value;
    });
  }

  void _handlePhoneTextFieldOnChange(String value) {
    setState(() {
      phoneInput = value;
    });
  }

  Rule get emailInputRule {
    return Rule(
      emailInput,
      name: 'Email',
      isAlphaSpace: true,
      isRequired: true,
      customErrorText: 'Invalid Email',
    );
  }

  Rule get phoneInputRule {
    return Rule(
      phoneInput,
      name: 'Phone',
      isPhone: true,
      isRequired: true,
      customErrorText: 'Invalid Phone',
    );
  }

  bool get isContinueBtnEnabled {
    final groupRule = GroupRule(
      [emailInputRule, phoneInputRule],
      name: 'Continue Button',
      requiredAll: true,
    );

    return !groupRule.hasError;
  }

  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            TextField(
              onChanged: (String value) {
                _handleEmailTextFieldOnChange(value);
              },
              decoration: InputDecoration(
                hintText: 'Email address',
                errorText: emailInputRule?.error ?? null,
              ),
            ),
            TextField(
              onChanged: (String value) {
                _handlePhoneTextFieldOnChange(value);
              },
              decoration: InputDecoration(
                hintText: 'Phone',
                errorText: phoneInputRule?.error ?? null,
              ),
            ),
            if (isContinueBtnEnabled)
              FlatButton(
                onPressed: () {
                  // call api
                },
                child: const Text('Continue'),
              )
            else
              Container()
          ],
        ),
      ),
    );
  }
}

Flutter Mobx

Mobx Store

class UpdateUserStore = _UpdateUserStoreBase
    with _$UpdateUserStore;

abstract class _UpdateUserStoreBase with Store {
  @observable
  String profileName;

  @observable
  String profileEmail;

  @computed
  Rules get profileNameInputRule {
    final rule = Rules(
      profileName,
      name: 'Name',
      isRequired: false,
      isAlphaSpace: true,
      customErrorText: 'Invalid name',
    );

    return rule;
  }

  @computed
  Rules get profileEmailInputRule {
    final rule = Rules(
      profileEmail,
      name: 'Name',
      isRequired: false,
      isEmail: true,
      customErrorText: 'Invalid email',
    );

    return rule;
  }

  @computed
  bool get isContinueButtonEnabled {
    final groupRule = GroupRules(
      [profileEmailInputRule, profileNameInputRule],
      name: 'Continue Button',
      requiredAll: true,
    );

    return !groupRule.hasError;
  }

  @action
  void setProfileName(String value) {
    profileName = value;
  }

  @action
  void setProfileEmail(String value) {
    profileEmail = value;
  }
}

Widget

class _UpdateUserScreen extends State<UpdateUserScreen> {
  UpdateUserStore _updateUserStore = UpdateUserStore();

  void _handleProfileNameOnChange(String value) {
    _updateUserStore.setProfileName(value);
  }

  void _handleProfileEmailOnChange(String value) {
    _updateUserStore.setProfileEmail(value);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: Display.getWidth(context),
      height: Display.getHeight(context),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Observer(
            builder: (_) {
              final _errorText = _updateUserStore.profileNameInputRule?.error;

              return TextField(
                onChanged: (String value) {
                  _handleProfileNameOnChange(value);
                },
                decoration: InputDecoration(
                  hintText: 'Name',
                  errorText: _errorText,
                ),
              );
            },
          ),
          Observer(
            builder: (_) {
              final _errorText = _updateUserStore.profileEmailInputRule?.error;

              return TextField(
                onChanged: (String value) {
                  _handleProfileEmailOnChange(value);
                },
                decoration: InputDecoration(
                  hintText: 'Email',
                  errorText: _errorText,
                ),
              );
            },
          ),
          Observer(
            builder: (_) {
              final _isContinueButtonEnabled =
                  _updateUserStore.isContinueButtonEnabled;

              if (!_isContinueButtonEnabled) {
                return Container();
              }

              return FlatButton(
                onPressed: () {},
                child: const Text('Continue'),
              );
            },
          ),
        ],
      ),
    );
  }
}

Changelogs

2.0.0

New feature:

  • Null safety
  • Added shouldPass
1.2.0+1

New feature:

  • Added trim, upperCase and lowerCase rule options
1.1.0

Breaking changes:

  • regex now expects a RegExp object instead of String

Buy me a coffee

Help me keep the app FREE and open for all. Paypal me: paypal.me/ganeshrvel

Contacts

Please feel free to contact me at [email protected]

About

License

Rules | Powerful and feature-rich validation library for both Dart and Flutter. MIT License.

Copyright © 2018 - Present Ganesh Rathinavel

You might also like...

Sample Flutter app for creating basic login forms validation for email and passwords

Sample Flutter app for creating basic login forms validation for email and passwords

Email validation Flutter example Sample flutter app showing how to validate a e-mail login form. This example uses the email_validator package for val

Dec 15, 2022

VerificaC19-flutter - Unofficial EU DGC validation package for Flutter

VerificaC19 package for Flutter About This package allows to decode and validate

Oct 21, 2022

Flutter Plugin - Simple character blocked input that usually used for redeem/validation input.

Flutter Plugin - Simple character blocked input that usually used for redeem/validation input.

Block Input Simple character blocked input that usually used for redeem code or validation code input. Gallery Example Full example import 'package:bl

Nov 2, 2022

A lightweight and powerful batch library written in Dart

A lightweight and powerful batch library written in Dart

A lightweight and powerful batch library written in Dart. You can easily develop a job schedule and batch program in Dart with this library.

Dec 13, 2022

Using all validation logics.

new_login A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if thi

Feb 28, 2022

Generate secure passwords, check for exposed passwords, get visual feedback for password strength or get form validation with a minimum password strength required.

Generate secure passwords, check for exposed passwords, get visual feedback for password strength or get form validation with a minimum password strength required.

password_strength_checker Generate secure passwords, check for exposed passwords, get visual feedback for password strength or get form validation wit

Aug 8, 2023

A Dart package which simulates pass by reference feature

Reference Wrapper is a Dart package which simulates pass by reference feature us

Dec 19, 2021

🎬 A movie catalog app for both Android & IOS ~ Flutter.io project in Dart | Dart, Bloc, Movies

Movie Catalog App 🎬 Browse through movies from the YIFY api Getting Started For help getting started with Flutter, view our online documentation. Tod

Nov 21, 2022

A powerful official extension library of Tab/TabBar/TabView, which support to scroll ancestor or child Tabs when current is overscroll, and set scroll direction and cache extent.

extended_tabs Language: English | 中文简体 A powerful official extension library of Tab/TabBar/TabView, which support to scroll ancestor or child Tabs whe

Dec 13, 2022
Comments
  • Improving isRequired validation

    Improving isRequired validation

    If the user has entered multiple empty lines with textField it'll consider it as non-empty value. so please make sure this behavior added to the isRequired property.

    opened by awnigharbia 8
  • Added shouldPass validation

    Added shouldPass validation

    Hi, @ganeshrvel! Thanks for the library. I love its Flutter- and UI-agnostic design and composability of the rules. Plays really well with state management in my project. The only thing I was missing was ability to use my own custom validation functions for some not-so-typical checks. So here goes the pull request with this feature.

    opened by anotherpit 5
  • Broken email regex

    Broken email regex

    No, please don't use this so-called "regex email validation".

    I can think of a few email addresses that are perfectly valid that do not match that regex. When you want to validate an email address, please do not use a hand-rolled (or even copy-n-pasted) regex.

    Instead, use (Dart) https://pub.dev/packages/email_validator (or Perl) https://metacpan.org/pod/Mail::RFC822::Address, which properly implements the RFC822 (et. seq.) address according to the rules in the RFC.

    (Jokingly...) Or, cut-n-paste this one (and only this one): http://www.ex-parrot.com/~pdw/Mail-RFC822-Address.html. Don't forget to remove the newlines from that 1400-char monster. :)

    opened by RandalSchwartz 0
  • another validation approach

    another validation approach

    https://github.com/joanpablo/reactive_forms

    I liked this one because it encompass the fact that validation has to happen client side but also server side often.

    opened by joe-getcouragenow 0
Owner
Ganesh Rathinavel
The J̶a̶v̶a̶S̶c̶r̶i̶p̶t̶ Dart Guy
Ganesh Rathinavel
Call Kit is a prebuilt feature-rich call component, which enables you to build one-on-one and group voice/video calls into your app with only a few lines of code.

Call Kit (ZegoUIKitPrebuiltCall) Call Kit is a prebuilt feature-rich call component, which enables you to build one-on-one and group voice/video calls

ZEGOCLOUD 9 Dec 26, 2022
Deepak Sharma 149 Dec 10, 2022
An opinionated, community-driven set of lint rules for Dart and Flutter projects. Like pedantic but stricter

Lint for Dart/Flutter lint is a hand-picked, open-source, community-driven collection of lint rules for Dart and Flutter projects. The set of rules fo

Pascal Welsch 257 Jan 3, 2023
Lint rules for Dart and Flutter used internally at NetGlade.

NetGlade Analysis Developed with ?? by NetGlade This package provides lint rules for Dart and Flutter which are used at NetGlade. Usage To use the lin

NetGlade 3 Dec 4, 2022
The Import Lint package defines import lint rules and report on lints found in Dart code.

Why import lint? The Import Lint package defines import lint rules and report on lints found in Dart code. ?? Usage Add import_lint as a dev_dependenc

ryo 15 Dec 9, 2022
Linter rules corresponding to the guidelines in Effective Dart

effective_dart This package is deprecated. Before it was deprecated, it was the way to provide analysis options corresponding to the guidelines in Eff

Honza Bittner 127 Dec 9, 2022
This library provides the easiest and powerful Dart/Flutter library for Mastodon API 🎯

The Easiest and Powerful Dart/Flutter Library for Mastodon API ?? 1. Guide ?? 1.1. Features ?? 1.2. Getting Started ⚡ 1.2.1. Install Library 1.2.2. Im

Mastodon.dart 55 Jul 27, 2023
A Dart validation DSL to validate your flutter app models.

Validations made simple A fp inspired validation DSL. For Dart and Flutter projects. Features Completely extensible (create your own combinators, vali

Daniel Cardona Rojas 26 Feb 8, 2022
Custom dropdown widget allows to add highly customizable widget in your projects with proper open and close animations and also comes with form required validation.

Custom Dropdown Custom Dropdown package lets you add customizable animated dropdown widget. Features Lots of properties to use and customize dropdown

Abdullah Chauhan 22 Dec 29, 2022
The lightweight and powerful wrapper library for Twitter Ads API written in Dart and Flutter 🐦

TODO: Put a short description of the package here that helps potential users know whether this package might be useful for them. Features TODO: List w

Twitter.dart 2 Aug 26, 2022