glade_forms 1.0.1 copy "glade_forms: ^1.0.1" to clipboard
glade_forms: ^1.0.1 copied to clipboard

A universal way to define form validators with support of translations.

netglade

Developed with 💚 by netglade

ci glade_forms license: MIT style: netglade analysis Discord


A universal way to define form validators with support of translations.

👀 What is this? #

Glade forms offer unified way to define reusable form input with support of fluent API to define input's validators and with support of translation on top of that.

📖 Glade Forms Widgetbook

🚀 Getting started #

Define you model and inputs:

class _Model extends GladeModel {
  late StringInput name;
  late GladeInput<int> age;
  late StringInput email;

  @override
  List<GladeInput<Object?>> get inputs => [name, age, email];

  @override
  void initialize() {
    name = StringInput.required();
    age = GladeInput.intInput(value: 0);
    email = StringInput.create(validator: (validator) => (validator..isEmail()).build());

    super.initialize();
  }
}

and wire-it up with Form

GladeFormBuilder(
  create: (context) => _Model(),
  builder: (context, model) => Form(
    autovalidateMode: AutovalidateMode.onUserInteraction,
    child: Column(
      children: [
        TextFormField(
          initialValue: model.name.value,
          validator: model.name.textFormFieldInputValidator,
          onChanged: model.name.updateValueWithString,
          decoration: const InputDecoration(labelText: 'Name'),
        ),
        TextFormField(
          initialValue: model.age.stringValue,
          validator: model.age.textFormFieldInputValidator,
          onChanged: model.age.updateValueWithString,
          decoration: const InputDecoration(labelText: 'Age'),
        ),
        TextFormField(
          initialValue: model.email.value,
          validator: model.email.textFormFieldInputValidator,
          onChanged: model.email.updateValueWithString,
          decoration: const InputDecoration(labelText: 'Email'),
        ),
        const SizedBox(height: 10),
        ElevatedButton(onPressed: model.isValid ? () {} : null, child: const Text('Save')),
      ],
    ),
  ),  
)

See 📖 Glade Forms Widgetbook , complex, examples.

GladeInput #

Each form's input is represented by instance of GladeInput<T> where T is value held by input. For simplicity we will interchange input and GladeInput<T>.

Every input is dirty or pure based on if value was updated (or not, yet).

On each input we define

  • validator - Input's value must satistfy validation to be valid input.
  • translateError - If there are validation errors, function for error translations can be provided.
  • inputKey - For debug purposes and dependencies, each input can have unique name for simple identification.
  • dependencies - Each input can depend on another inputs for validation.
  • valueConverter - If input is used by TextField and T is not a String, value converter should be provided.
  • valueComparator - Sometimes it is handy to provied initialValue which will be never updated after input is mutated. valueComparator should be provided to compare initialValue and value if T is not comparable type by default.
  • defaultTranslation - If error's translations are simple, the default translation settings can be set instead of custom translateError method.

Defining input #

Most of the time, input is created with .create() factory with defined validation, translation and other properties.

Validation is defined through part methods on ValidatorFactory such as notNull(), satisfy() and other parts.

Each validation rule defines

  • value validation, e.g notNull() defines that value can not be null. satisfy() defines predicate which has to be true to be valid etc.
  • devErrorMessage - message which will be displayed if no translation is not provided.
  • key - Validation error's identification. Usable for translation.

This example defines validation that int value has to be greater or equal to 18.

 ageInput = GladeInput.create(
      validator: (v) => (v
            ..notNull()
            ..satisfy(
              (value, extra, dependencies) {
                return value >= 18;
              },
              devError: (_, __) => 'Value must be greater or equal to 18',
              key: _ErrorKeys.ageRestriction,
            ))
          .build(),
      value: 0,
      valueConverter: GladeTypeConverters.intConverter,
 );

Order of validation parts matter. By default first failing part stops validation.

Pass stopOnFirstError: false on .build() to validate all parts at once.

StringToValueConverter (valueConverter) #

As noted before, if T is not a String, a converter from String to T has to be provided.

GladeForms provides some predefined converters such as IntConverter and more. See GladeTypeConverters for more.

StringInput #

StringInput is specialized variant of GladeInput

Dependencies #

Input can have dependencies on another inputs to allow dependendent validation. inputKey should be assigned for each input to allow dependency work.

In validation (or translation if needed) just call dependencies.byKey() to get dependendent input.

📚 Adding translation support #

Each validation error (and conversion error if any) can be translated. Provide translateError function which accepts

  • error - Error to translate
  • key - Error's identification if any
  • devMessage - Provided devError from validator
  • dependencies - Input's dependencies

Age example translation (LocaleKeys are generated translations from easy_localization package)

translateError: (error, key, devMessage, {required dependencies}) {
  if (key == _ErrorKeys.ageRestriction) return LocaleKeys.ageRestriction_under18.tr();

  if (error.isConversionError) return LocaleKeys.ageRestriction_ageFormat.tr();

  return devMessage;
}

GladeModel #

GladeModel is base class for Form's model which holds all inputs together. It is useful for cases where you want to sum up validations at once, like disabling save button until all inputs are valid.

GladeModel is ChangeNotifier so all dependant widgets will be rebuilt.

There are several rules how to define models

  • Each input has to be mutable and late field
  • Model has to override initialize method where each input field is created
  • In the end of initialize method, super.initialize() must be called to wire-up inputs with model.

⚠️ Without wiring-up model, model will not be updated appropiately and properties such as isValid or formattedErrors will not work.

For updating input call either updateValueWithString(String?) to update T value with string (will be converted if needed) or set value directly (via setter).

GladeFormBuilder and GladeFormProvider #

GladeModelProvider is predefined widget to provide GladeModel to widget's subtreee.

Similarly GladeFormBuilder allows to listen to model's changes and rebuilts its child.

🔨 Debugging validators #

There are some getters and methods on GladeInput / GladeModel which can be used for debugging.

Use model.formattedValidationErrors to get all input's error formatted for simple debugging.

There is also GladeModelDebugInfo widget which displays table of all model's inputs and their properties such as isValid or validation error.

Using validators without GladeInput #

It is possible to use GladeValidator without associated GladeInputs.

Just create instance of GladeValidator (or StringValidator) and use it.

final validator = (StringValidator()..notEmpty()).build();
final result = validator.validate(null);

👏 Contributing #

Your contributions are always welcome! Feel free to open pull request.

12
likes
0
pub points
54%
popularity

Publisher

verified publishernetglade.cz

A universal way to define form validators with support of translations.

Repository (GitHub)
View/report issues

License

unknown (LICENSE)

Dependencies

collection, equatable, flutter, meta, provider

More

Packages that depend on glade_forms