easy_form_bloc 0.1.0
easy_form_bloc: ^0.1.0 copied to clipboard

Bloc form is lightweight library which helps you writing forms and avoid boilerplate. Library cover domain layer of forms so when using it you are still able to use your own view implementation.

easy_form_bloc pub package #

Make writing forms easier with BLoC pattern!

Bloc form is lightweight library which helps you writing forms and avoid boilerplate.
Library cover domain layer of forms so when using it you are still able to use your own view implementation.

Already done #

  • [x] Generic type fields
  • [x] Synchronous Field Validation
  • [X] Asynchronous Field Validation
  • [X] Mapping value to Map<String,dynamic>
  • [X] Single step forms with multiple fields
  • [X] Skippable steps with callbacks
  • [X] Synchronous Form Submitting
  • [X] Asynchronous Form Submitting
  • [X] States for loading, errors

Things to implement #

  • [ ] Predefined validators
  • [ ] Form Factory to make creating form easier
  • [ ] Cover codebase with unit tests
  • [ ] Create group fields fe. password repeat etc.
  • [ ] Add support for creating forms from JSON data

Getting started #

Library was created with plan to make it as flexible as possible to align to many cases. Basically forms contains fields. Fields sometimes are not required, values entered by user mostly should be validated. Value can be validate maybe by some repository or just with some synchronized function. When all required fields are valid we would like to give user possibility to submit form with entered data. Sounds very common? It could be login form, one page registration form or anything simillar.

Single page form - Example#1 #

In that case we would like to implement simple registration form with fields:

  • Login field - validated with async repository,
  • Password field - validated locally,
  • Required term checkbox,
  • Not required GiODO checkbox

At begin we have to create our fields BLoCs. Login field:

  FormFieldBloc<String> _loginFieldBloc = FormFieldBloc(
      defaultValue: "",
      valueKey: "login",
      futureValidator: (value) => ValidationRepository.asyncValidation(value),
      isRequired: true);

Password field:

  FormFieldBloc<String> _passwordFieldBloc = FormFieldBloc(
      defaultValue: "",
      valueKey: "password",
      futureValidator: (value) => ValidationRepository.asyncValidation(value),
      isRequired: true);

Terms field:

  FormFieldBloc<bool> _termsFieldBloc = FormFieldBloc(
      defaultValue: false,
      valueKey: "terms",
      validator: (value) => value,
      isRequired: true);

Giodo field:

  FormFieldBloc<bool> _giodoFieldsBloc = FormFieldBloc(
      defaultValue: false,
      valueKey: "giodo",
      validator: (value) => value,
      isRequired: false);

When fields BLoC's are already setup all what you need to do is make your views add event inside thoose BLoC's. To do that you have to call f.e.

_passwordFieldBloc.add(FieldValueChanged("abc"));

Then whole "magic" happens, BLoC is emitting state that field is under validation and it's called FormFieldValidating then when validation is finished based on result it emmits FormFieldValidationFailed or FormFieldValid and that's all, based on that you can show anything you would like, f.e. loading indicators etc, error messages/pop ups. To connect many fields into form you need to use SingleStepForm class. To create that you have to pass, fields bloc's, and submit function or submit future. In our case it is how it looks like

_formBloc = SingleStepFormBloc(
        fields: {
          _loginFieldBloc,
          _passwordFieldBloc,
          _termsFieldBloc,
          _giodoFieldBloc
        }.toList(),
        submitFuture: (values) => ValidationRepository.validateForm(values));

FormBloc is subscribing to all fields blocs stream and tracking that is it possible to make whole form Enabled or Disabled and base on that emitting FormNotValidForSubmit or FormValidForSubmit. To submit form you have to add SubmitForm event into FormBloc.

_formBloc.add(SubmitForm());

When that happens BLoC is emitting FormSubmittingLoading state what allows you to show loading indicators etc. and then it calls submitFuture or submitFunction with form data. Form data is in Map<String,dynamic> in our case it is how example data passed for submit function/future will looks like

{
"login": "abc@abc.com",
"password": "admin",
"terms": true,
"giodo": false
}

and that's all for whole easy registration case. The only thing you need to add is UI and your own way to build that based on state. Below is example how that BLoC could be used.

Multiple page form - Example#2 #

Sometimes single page form is not enough to cover our case. Because we would like to split our form into few steps, some could be skippable and at the end of form we would like to user submit form with data from all steps and be sure that our user went through all steps correctly.

In this example we will have to cover specified case:

  • First step with simple required fields - Name, surname,
  • In second step we will ask user for his favourite color,
  • Last step will also have required fields - Username, password

First step #

We will begin with creating Fields BLoC's so we will have:

  final FormFieldBloc<String> _nameFieldBloc = FormFieldBloc(
      defaultValue: "",
      valueKey: "name",
      validator: (value) => value.isNotEmpty,
      isRequired: true);
  
  final FormFieldBloc<String> _surNameFieldBloc = FormFieldBloc(
      defaultValue: "",
      valueKey: "surName",
      validator: (value) => value.isNotEmpty,
      isRequired: true);
  
  final FormFieldBloc<Color> _favouriteColorFieldBloc = FormFieldBloc(
      defaultValue: Colors.white,
      valueKey: "favouriteColor",
      validator: (value) => true,
      isRequired: false,
      transformValueToMap: (color) => {"colorCode": color.value});
  
  final FormFieldBloc<String> _loginFieldBloc = FormFieldBloc(
      defaultValue: "",
      valueKey: "login",
      validator: (value) => value.isNotEmpty,
      isRequired: true);
  
  final FormFieldBloc<String> _passwordFieldBloc = FormFieldBloc(
      defaultValue: "",
      valueKey: "password",
      validator: (value) => Validator.isPasswordValid(value),
      isRequired: true);

Second step #

In second step we will create BLoC's for form steps and form itself:

  _firstStepBloc = FormStepBloc(
        fieldsBlocs: [_nameFieldBloc, _surNameFieldBloc],
        submitStepCallback: _navigateToNextStep);

    _secondStepBloc = FormStepBloc(
        fieldsBlocs: [_favouriteColorFieldBloc],
        skipStepCallback: _navigateToNextStep,
        submitStepCallback: _navigateToNextStep);
    
    _thirdStepBloc = FormStepBloc(
        fieldsBlocs: [_loginFieldBloc, _passwordFieldBloc],
        submitStepCallback: _navigateToNextStep);
    
    _formBloc = MultiStepsFormBloc(
        formSteps: [_firstStepBloc, _secondStepBloc, _thirdStepBloc],
        submitFuture: Repository.submitForm);

As you can see above it's possible to pass skipping and submitting step callback it allows you to f.e. navigate to next screen, log something etc.

Final step #

So that's all now you have to only write your own UI and enjoy with working forms without writing whole boilerplate cause I did it for you :)

Documentation #

FormFieldBloc

That class should be use as your domain layer for single field in your form. It is generic so with it you can handle of type of data.

Constructor parameters:

NameTypeDescriptionRequired
defaultValueT(generic)Default value of fieldyes
valueKeyStringKey will be used to create Map<String,dynamic> when form will be submittedyes
validatorbool Function(T value)Function which will validate field value and return boolean resultyes if futureValidator is null
futureValidatorFutureFunction which will return future which will validate field value and return async resultyes if validator is null
isRequiredboolIndicates if field is required to make form step or form valid for submit (if field is not required and it will be not valid the value will be not used)optional (default true)
transformValueToMapMap<String,dynamic> Function(T)Function which will transform value into Map to be used for Form submitting. Useful with not primitive types.optional

Possible events: #

FieldValueChanged

That event should be added to bloc every time when you would like to update BLoC field data.
Constructor params:

NameTypeDescriptionRequired
valueT (generic)Current value of fieldyes

ClearField

That event will reset value of field to default one and BLoC will emit FormFieldEmpty state.

Possible states: #

All possible fields states inherit FieldFormState class which has field value which is generic typed.

FormFieldNotValid

That state will be emitted when validation after value changed will return false.

FormFieldEmpty

That state will be emitted when current value is equal to default one.

FormFieldValidating

That state will be emitted when validation of field is in progress.

FormFieldValidationFailed

This state will be emitted when validation throw exception.

Constructor params:

NameTypeDescriptionRequired
exceptionExceptionException happend during validationyes

FormFieldValid

This state will be emitted when value was validated with success

NameTypeDescriptionRequired
fieldKeyStringField used to create key:value map when submitting formyes
valueMapMap<String,dynamicThat value is optional and will be not null when we want to map our value to map with our transformValueToMap functionno

FormStepBloc #

That is based class for whole form. It is responsible for grouping fields/steps and based on theirs states emits valid state

Constructor parameters:

NameTypeDescriptionRequired
submitFunctionFunction(Map<String, dynamic>)here we pass function which will be used for submiting form with valid datayes if submitFuture is null
submitFutureFuture Function(Map<String, dynamic>)here we pass function which will be used for async submiting form with valid datayes if submitFunction is null

You can extend that class on your own to implement forms but there are 2 already prepared classes:

SingleStepFormBloc extends FormStepBloc #

That class is prepared to handle Forms with many fields but only with one steps. It is listening to all fields blocs and if all required are valid form itself is also valid for submitting.

Constructor parameters:

NameTypeDescriptionRequired
submitFunctionFunction(Map<String, dynamic>)here we pass function which will be used for submiting form with valid datayes if submitFuture is null
submitFutureFuture Function(Map<String, dynamic>)here we pass function which will be used for async submiting form with valid datayes if submitFunction is null
fieldsList<FormFieldBloclist of fields blocs which form is containingyes

MultiStepFormBloc extends FormStepBloc #

That class is prepared to handle many steps with many forms inside. It is listening to a That class is prepared to handle many steps with many forms inside. It is listening to all steps blocs and if all are valid or skipped form itself is also valid for submitting.ll steps blocs and if all are valid or skipped form itself is also valid for submitting.

Constructor parameters:

NameTypeDescriptionRequired
submitFunctionFunction(Map<String, dynamic>)here we pass function which will be used for submiting form with valid datayes if submitFuture is null
submitFutureFuture Function(Map<String, dynamic>)here we pass function which will be used for async submiting form with valid datayes if submitFunction is null
formStepsListlist of forms which form is containingyes

Possible events: #

DisableForm

That event is using for making forms disabled.

EnableForm

That event is using for making forms enabled.

SubmitForm

That event is using for submitting form, after that event is added FormBloc is calling submitFunction or submitFuture.

Possible states: #

FormNotValidForSubmit

That state is initial one and will be emitted when not all steps/fields are valid.

FormValidForSubmit

That state will be emitted when all required fields are valid and/or all steps are valid/skipped.

FormSubmittingLoading

That state will be emitted after SubmitForm event was added and before calling submitFunction or submitFuture.

FormSubmitted extends FormValidForSubmit

That state will be emitted when form submittion was successful. 

Constructor params:
NameTypeDescriptionRequired
dataSubmittedMap<String,dynamic>That fields contains data which was submittedyes

FailedFormSubmitted

That state will be emitted when submitFunction or submitFuture throws exception. 

Constructor params:
NameTypeDescriptionRequired
exExceptionException which was thrown during submissionyes
dataSubmittedMap<String,dynamic>That fields contains data which was submittedyes

Contribution ❤ #

Issues and pull requests are welcome

Please file feature requests and bugs at the issue tracker.

10
likes
80
pub points
24%
popularity

Publisher

patrykgalazka.pl

Bloc form is lightweight library which helps you writing forms and avoid boilerplate. Library cover domain layer of forms so when using it you are still able to use your own view implementation.

Repository (GitHub)
View/report issues

Documentation

API reference

License

Apache 2.0 (LICENSE)

Dependencies

bloc, equatable, flutter, rxdart

More

Packages that depend on easy_form_bloc