Giter VIP home page Giter VIP logo

reactive_forms_generator's Introduction

Pub

reactive_forms_generator

ReactiveFormsGenerator

Welcome to ReactiveFormsGenerator, code generator for reactive_forms which will save you tons of time and make your forms type safe.

There is no reason to write code manually! Let the code generation work for you.

Table of Contents

Motivation

One of the goals of this package is to make reactive_forms package even more cool and fun to use.

Let's see what issues this package tries to mitigate.

Here is how typical reactive_forms form looks like

/// form instantiation
FormGroup buildForm() =>
    fb.group(<String, Object>{
      'email': FormControl<String>(
        validators: [Validators.required, Validators.email],
      ),
      'password': ['', Validators.required, Validators.minLength(8)],
      'rememberMe': false,
    });

/// form itself
final form = ReactiveFormBuilder(
  form: buildForm,
  builder: (context, form, child) {
    return Column(
      children: [
        ReactiveTextField<String>(
          formControlName: 'email',
        ),
        const SizedBox(height: 16.0),
        ReactiveTextField<String>(
          formControlName: 'password',
        ),
        const SizedBox(height: 16.0),
        ElevatedButton(
          onPressed: () {
            if (form.valid) {
              print(form.value);
            } else {
              form.markAllAsTouched();
            }
          },
          child: const Text('Sign Up'),
        ),
        ElevatedButton(
          onPressed: () =>
              form.resetState({
                'email': ControlState<String>(value: null),
                'password': ControlState<String>(value: null),
                'rememberMe': ControlState<bool>(value: false),
              }, removeFocus: true),
          child: const Text('Reset all'),
        ),
      ],
    );
  },
);
  1. First issue is String identifiers which are used to define fields. Technically you can extract them into separate class, enum or whatever you like. But this is manual work which you have to do each time you create the form. The other disadvantage is when you refer to any field by this String identifier you loose static type check. There is no way for static analyser to check if some random field name login is suitable to put in particular widget. So you can easily get the form which looks ok but fails to build due to the typo in field names and putting the login field into ReactiveCheckbox field. Isn't it better to let the code generation to do it for you?

  2. Second issue is output which is always Map<String, Object>. It is ok for languages like JS. But for the typed language you would prefer to get the output fom the form like model. And avoid manual type casting like this one.

final document = DocumentInput(
  subTypeId: form.value["subType"] as DocumentSubTypeMixin,
  documentNumber: form.value["documentNumber"] as String,
  countryIsoCode: form.value["country"] as CountryMixin,
  countryOfIssueIsoCode: form.value["country"] as CountryMixin,
  issueDate: form.value["issueDate"] as DateTime,
  vesselId: form.value["vessel"] as VesselMixin,
);

These are two main issues that forced me to write this generator. In the next chapters of documentation you'll see how we define and annotate the model which describes the form state and how easy and elegant it works with a bit of magic from code generation.

How to use

Minimum Requirements

  • Dart SDK: >=2.12.0 <3.0.0
  • Flutter: >= 2.2.0

Installation

To use [reactive_forms_generator], you will need your typical [build_runner]/code-generator setup.
First, install [build_runner] and [reactive_forms_generator] by adding them to your pubspec.yaml file:

# pubspec.yaml
dependencies:
  reactive_forms:
  reactive_forms_annotations:

dev_dependencies:
  build_runner:
  reactive_forms_generator:

This installs three packages:

Ignore lint warnings on generated files

It is likely that the code generated by [reactive_forms_generator] will cause your linter to report warnings.

The solution to this problem is to tell the linter to ignore generated files, by modifying your analysis_options.yaml:

analyzer:
  exclude:
    - "**/*.gform.dart"

Run the generator

Note that like most code-generators, reactive_forms_generator will need you to both import the annotation (meta) and use the part keyword on the top of your files.

Make sure you added required imports before running the generator

import 'package:flutter/material.dart';
import 'package:reactive_forms/reactive_forms.dart';
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

part 'my_file.gform.dart';

To run the code generator you have two possibilities:

  • If your package depends on Flutter:
    • flutter pub run build_runner build
  • If your package does not depend on Flutter:
    • dart pub run build_runner build

Features

Basics

Let's start from simple login form.

First we need to define our form model

Model

class Basic {
  final String email;

  final String password;

  Basic({this.email = '', this.password = ''});
}

We defined here a simple model with non-nullable email and password fields.

Annotation

The next step is to add annotations to help generator do his job.

import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

@Rf()
class Basic {
  final String email;

  final String password;

  Basic({
    @RfControl() this.email = '',

    @RfControl() this.password = '',
  });
}

ReactiveFormAnnotation - tells the generator that we want to Form based on this model. FormControlAnnotation - maps fields to control elements.

Validation

The login form should not proceed if there is any empty values. We need to modify our code to add some required validators.

import 'package:example/helpers.dart';
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

@Rf()
class Basic {
  final String email;

  final String password;

  Basic({
    @RfControl(
      validators: const [RequiredValidator()],
    ) this.email = '',
    @RfControl(
      validators: const [RequiredValidator()],
    ) this.password = '',
  });
}

As far as we are using annotations - validators should be top level functions or static class fields or const constructors.

Now we are ready to run our form generator. .

Form

Let's build our form based on generated code

final form = BasicFormBuilder(
  // setup form model with initial data
  model: Basic(),
  // form builder
  builder: (context, formModel, child) {
    return Column(
      children: [
        ReactiveTextField<String>(
          formControl: formModel.emailControl,
          validationMessages: {
            ValidationMessage.required: (_) => 'The email must not be empty',
          },
          decoration: const InputDecoration(labelText: 'Email'),
        ),
        const SizedBox(height: 8.0),
        ReactiveTextField<String>(
          formControl: formModel.passwordControl,
          obscureText: true,
          validationMessages: {
            ValidationMessage.required: (_) => 'The password must not be empty',
          },
          textInputAction: TextInputAction.done,
          decoration: const InputDecoration(labelText: 'Password'),
        ),
        const SizedBox(height: 8.0),
        ReactiveBasicFormConsumer(
          builder: (context, form, child) {
            return ElevatedButton(
              child: Text('Submit'),
              onPressed: form.form.valid
                  ? () {
                print(form.model.email);
                print(form.model.password);
              }
                  : null,
            );
          },
        ),
      ],
    );
  },
);

BasicFormBuilder - generated widget that injects form into context ReactiveTextField - bundled text fields ReactiveBasicFormConsumer - generated widget that rebuilds upon form change

You can get access to prefilled form model by calling form.model.[field-name].

Dynamic forms with FormArray

The next example will show how to build dynamic forms. We will create a mailing list which will allow adding new email and basic validation.

Model

The model is pretty simple.

class MailingList {
  final List<String?> emailList;

  MailingList({
    this.emailList = const [],
  });
}

Annotation

The next step is to add annotations to help generator do his job.

import 'package:example/helpers.dart';
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

@Rf()
class MailingList {
  final List<String?> emailList;

  MailingList({
    @RfArray(
      validators: const [
        MailingListValidator(),
      ],
    ) this.emailList = const [],
  });
}

ReactiveFormAnnotation - tells the generator that we want to Form based on this model. FormArrayAnnotation - maps fields to control elements.

Validation

The mailing list form should not be valid in two cases - if there are duplicates and if any field is invalid email.

class MailingListValidator extends Validator<dynamic> {
  const MailingListValidator() : super();

  @override
  Map<String, dynamic>? validate(AbstractControl control) {
    final formArray = control as FormArray<String>;
    final emails = formArray.value ?? [];
    final test = <String>{};

    final result = emails.fold<bool>(true,
            (previousValue, element) => previousValue && test.add(element ?? ''));

    return result ? null : <String, dynamic>{'emailDuplicates': true};
  }
}

As far as we are using annotations - validators should be top level functions or static class fields.

Now we are ready to run our form generator.

Form

Let's build our form based on generated code

// create form based on generated widget
final form = MailingListFormBuilder(
  // instantiate with empty model
  model: MailingList(),
  builder: (context, formModel, child) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Expanded(
              // renders list of fields corresponding to added elements
              child: ReactiveFormArray<String>(
                formArray: formModel.emailListControl,
                builder: (context, formArray, child) =>
                    Column(
                      children: formModel.emailListValue
                          .asMap()
                          .map((i, email) {
                        return MapEntry(
                            i,
                            ReactiveTextField<String>(
                              formControlName: i.toString(),
                              validationMessages: {
                                'email': (_) => 'Invalid email',
                              },
                              decoration: InputDecoration(
                                  labelText: 'Email ${i}'),
                            ));
                      })
                          .values
                          .toList(),
                    ),
              ),
            ),
            SizedBox(width: 16),
            // adds new item to the list of fields
            ElevatedButton(
              onPressed: () {
                // add****Item function allows you not only to add values but also alter the FormControl parameters if needed
                // you can change 
                //  - validators,
                //  - asyncValidators,
                //  - asyncValidatorsDebounceTime,
                //  - disabled,
                // validatorsApplyMode - controls how the validators will be applied
                //  - merge - will append to the validators defined in annotation
                //  - override - will override to the validators defined in annotation
                formModel.addEmailListItem('');
              },
              child: const Text('add'),
            )
          ],
        ),
        SizedBox(height: 16),
        // renders error related to the whole list of elements
        ReactiveMailingListFormConsumer(
          builder: (context, form, child) {
            // map error keys to text
            final errorText = {
              'emailDuplicates': 'Two identical emails are in the list',
            };
            final errors = <String, dynamic>{};

            // filter values related to individual text fields
            form.emailListControl.errors.forEach((key, value) {
              final intKey = int.tryParse(key);
              if (intKey == null) {
                errors[key] = value;
              }
            });

            // if there is still erros left - render an error message 
            if (form.emailListControl.hasErrors && errors.isNotEmpty) {
              return Text(errorText[errors.entries.first.key] ?? '');
            } else {
              return Container();
            }
          },
        ),
        SizedBox(height: 16),
        Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            ElevatedButton(
              onPressed: () {
                if (formModel.form.valid) {
                  print(formModel.model);
                } else {
                  formModel.form.markAllAsTouched();
                }
              },
              child: const Text('Sign Up'),
            ),
            ReactiveMailingListFormConsumer(
              builder: (context, form, child) {
                return ElevatedButton(
                  child: Text('Submit'),
                  onPressed: form.form.valid ? () {} : null,
                );
              },
            ),
          ],
        )
      ],
    );
  },
);

Nested forms with FormGroups

The next example will show how to build nested forms. We will create a user profile form with first/last names and home/office addresses. Address will contain city/street/zip fields.

Model

The model will be separated on two parts UserProfile and Address

class UserProfile {
  final String firstName;

  final String lastName;

  final Address? home;

  final Address? office;

  UserProfile({
    this.firstName = '',
    this.lastName = '',
    this.home,
    this.office,
  });
}

class Address {
  final String? street;

  final String? city;

  final String? zip;

  Address({
    this.street,
    this.city,
    this.zip,
  });
}

Annotation

The next step is to add annotations to help generator do his job.

import 'package:example/helpers.dart';
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

@Rf()
class UserProfile {
  final String firstName;

  final String lastName;

  final Address? home;

  final Address? office;

  UserProfile({
    @RfControl(
      validators: const [RequiredValidator()],
    ) this.firstName = '',
    @RfControl(
      validators: const [RequiredValidator()],
    ) this.lastName = '',
    this.home,
    this.office,
  });
}

@RfGroup()
class Address {
  final String? street;

  final String? city;

  final String? zip;

  Address({
    @RfControl() this.street,
    @RfControl(
      validators: const [RequiredValidator()],
    ) this.city,
    @RfControl() this.zip,
  });
}

ReactiveFormAnnotation - tells the generator that we want to Form based on this model. FormGroupAnnotation - describes the nested form.

Validation

As far as we are using annotations - validators should be top level functions or static class fields.

Now we are ready to run our form generator. .

Form

Let's build our form based on generated code

// create form based on generated widget
final form = UserProfileFormBuilder(
  model: UserProfile(),
  builder: (context, formModel, child) {
    return SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          ReactiveTextField<String>(
            formControl: formModel.firstNameControl,
            validationMessages: {
              ValidationMessage.required: (_) => 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'First name',
            ),
          ),
          const SizedBox(height: 8.0),
          ReactiveTextField<String>(
            formControl: formModel.lastNameControl,
            validationMessages: {
              ValidationMessage.required: (_) => 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'Last name',
            ),
          ),
          const SizedBox(height: 24.0),
          Text('Home address', style: TextStyle(fontSize: 18)),
          ReactiveTextField<String>(
            formControl: formModel.homeForm.cityControl,
            validationMessages: {
              ValidationMessage.required: (_) => 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'Home city',
            ),
          ),
          const SizedBox(height: 8.0),
          ReactiveTextField<String>(
            formControl: formModel.homeForm.streetControl,
            validationMessages: {
              ValidationMessage.required: (_) => 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'Home street',
            ),
          ),
          const SizedBox(height: 8.0),
          ReactiveTextField<String>(
            formControl: formModel.homeForm.zipControl,
            validationMessages: {
              ValidationMessage.required: (_) => 'Must not be empty',
            },
            textInputAction: TextInputAction.done,
            decoration: const InputDecoration(
              labelText: 'Home zip',
            ),
          ),
          const SizedBox(height: 8.0),
          Text('Office address', style: TextStyle(fontSize: 18)),
          const SizedBox(height: 8.0),
          ReactiveTextField<String>(
            formControl: formModel.officeForm.cityControl,
            validationMessages: {
              ValidationMessage.required: (_) => 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'Office city',
            ),
          ),
          const SizedBox(height: 8.0),
          ReactiveTextField<String>(
            formControl: formModel.officeForm.streetControl,
            validationMessages: {
              ValidationMessage.required: (_) => 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'Office street',
            ),
          ),
          const SizedBox(height: 8.0),
          ReactiveTextField<String>(
            formControl: formModel.officeForm.zipControl,
            validationMessages: {
              ValidationMessage.required: (_) => 'Must not be empty',
            },
            decoration: const InputDecoration(
              labelText: 'Office zip',
            ),
          ),
          ElevatedButton(
            onPressed: () {
              if (formModel.form.valid) {
                print(formModel.model);
              } else {
                formModel.form.markAllAsTouched();
              }
            },
            child: const Text('Sign Up'),
          ),
          ReactiveUserProfileFormConsumer(
            builder: (context, form, child) {
              return ElevatedButton(
                child: Text('Submit'),
                onPressed: form.form.valid
                    ? () {
                  print(form.model.firstName);
                  print(form.model.lastName);
                }
                    : null,
              );
            },
          ),
        ],
      ),
    );
  },
);

Nested forms with array of FormGroups

The next example will show how to build nested forms. We will create a delivery list with simple control for name and form group for address; Address will contain city/street fields.

Model

The model will be separated on three parts DeliveryList, DeliveryPoint and Address

class DeliveryList {
  final List<DeliveryPoint> deliveryList;

  DeliveryList({
    this.deliveryList = const [],
  });
}

class DeliveryPoint {
  final String name;

  final Address? address;

  DeliveryPoint({
    this.name = '',
    this.address,
  });
}

class Address {
  final String? street;

  final String? city;

  Address({
    this.street,
    this.city,
  });
}

Annotation

The next step is to add annotations to help generator do his job.

@Rf()
class DeliveryList {
  final List<DeliveryPoint> deliveryList;

  DeliveryList({
    @RfArray() this.deliveryList = const [],
  });
}

@RfGroup()
class DeliveryPoint {
  final String name;

  final Address? address;

  DeliveryPoint({
    @RfControl(
      validators: const [RequiredValidator()],
    ) this.name = '',
    this.address,
  });
}

@RfGroup()
class Address {
  final String? street;

  final String? city;

  Address({
    @RfControl(
      validators: const [RequiredValidator()],
    ) this.street,
    @RfControl() this.city,
  });
}

ReactiveFormAnnotation - tells the generator that we want to Form based on this model. FormGroupAnnotation - describes the nested form.

Now we are ready to run our form generator. .

Form

Let's build our form based on generated code

// create form based on generated widget
final form = DeliveryListFormBuilder(
  model: DeliveryList(),
  builder: (context, formModel, child) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Expanded(
              child: ReactiveFormArray<Map<String, Object?>>(
                formArray: formModel.deliveryListControl,
                builder: (context, formArray, child) {
                  return Column(
                    children: formModel.deliveryListValue
                        .asMap()
                        .map((i, deliveryPoint) {
                      return MapEntry(
                          i,
                          Column(
                            children: [
                              ReactiveTextField<String>(
                                formControlName: '${i}.name',
                                validationMessages: {
                                  ValidationMessage.required: (_) => 'Must not be empty',
                                },
                                decoration: InputDecoration(
                                  labelText: 'Name ${i}',
                                ),
                              ),
                              ReactiveTextField<String>(
                                formControlName:
                                '${i}.address.street',
                                validationMessages: {
                                  ValidationMessage.required: (_) => 'Must not be empty',
                                },
                                decoration: InputDecoration(
                                  labelText: 'Street ${i}',
                                ),
                              ),
                              ReactiveTextField<String>(
                                formControlName: '${i}.address.city',
                                validationMessages: {
                                  ValidationMessage.required: (_) => 'Must not be empty',
                                },
                                decoration: InputDecoration(
                                  labelText: 'City ${i}',
                                ),
                              ),
                            ],
                          ));
                    })
                        .values
                        .toList(),
                  );
                },
              ),
            ),
            SizedBox(width: 16),
            ElevatedButton(
              onPressed: () {
                formModel.addDeliveryListItem(DeliveryPoint());
              },
              child: const Text('add'),
            )
          ],
        ),
        SizedBox(height: 16),
        SizedBox(height: 16),
        Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            ElevatedButton(
              onPressed: () {
                if (formModel.form.valid) {
                  print(formModel.model);
                } else {
                  formModel.form.markAllAsTouched();
                }
              },
              child: const Text('Sign Up'),
            ),
            ReactiveDeliveryListFormConsumer(
              builder: (context, form, child) {
                return ElevatedButton(
                  child: Text('Submit'),
                  onPressed: form.form.valid ? () {} : null,
                );
              },
            ),
          ],
        )
      ],
    );
  },
);

Freezed

You can annotate models produced by Freezed with additional annotations to generate the form Read more about freezed here

First we need to define our form model

Model

@freezed
class FreezedClass with _$FreezedClass {
  const factory FreezedClass({
    String? id,
    String? name,
    double? year,
  }) = _FreezedClass;

  factory FreezedClass.fromJson(Map<String, dynamic> json) =>
      _$FreezedClassFromJson(json);
}

Annotation

The next step is to add annotations to help generator do his job.

import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'freezed_class.g.dart';

part 'freezed_class.freezed.dart';

@freezed
@Rf()
class FreezedClass
    with _$FreezedClass {
  const factory FreezedClass({
    @RfControl() String? id,
    @RfControl() String? name,
    @RfControl() double? year,
  }) = _FreezedClass;

  factory FreezedClass.fromJson(Map<String, dynamic> json) =>
      _$FreezedClassFromJson(json);
}

Validation

You can add validations the same way as in basics example

Form

Let's build our form based on generated code

final form = FreezedClassFormBuilder(
  model: FreezedClass(),
  builder: (context, formModel, child) {
    return Column(
      children: [
        ReactiveTextField<String>(
          formControl: formModel.idControl,
          textInputAction: TextInputAction.next,
          decoration: const InputDecoration(
            labelText: 'ID',
            helperText: '',
            helperStyle: TextStyle(height: 0.7),
            errorStyle: TextStyle(height: 0.7),
          ),
        ),
        const SizedBox(height: 16.0),
        ReactiveTextField<String>(
          formControl: formModel.nameControl,
          textInputAction: TextInputAction.done,
          decoration: const InputDecoration(
            labelText: 'Name',
            helperText: '',
            helperStyle: TextStyle(height: 0.7),
            errorStyle: TextStyle(height: 0.7),
          ),
        ),
        const SizedBox(height: 16.0),
        ReactiveSlider(
          formControl: formModel.yearControl,
          min: 1900,
          max: 2100,
        ),
        ElevatedButton(
          onPressed: () {
            if (formModel.form.valid) {
              print(formModel.model);
              print(formModel.model.year);
            } else {
              formModel.form.markAllAsTouched();
            }
          },
          child: const Text('Sign Up'),
        ),
        ReactiveFreezedClassFormConsumer(
          builder: (context, formModel, child) {
            return ElevatedButton(
              child: Text('Submit'),
              onPressed: formModel.form.valid ? () {} : null,
            );
          },
        ),
      ],
    );
  },
);

Subforms

There could be the case when some of your nested entities will have to have their own subform with subset of fields. Let's refer to our previous example of Nested forms with array of FormGroups

We used DeliveryPoint entity to create a list for points for delivery. If you want to have the set of form classes to be generated for DeliveryPoint just add and additional ReactiveFormAnnotation annotation

@Rf()
@RfGroup()
class DeliveryPoint {
  final String name;

  final Address? address;

  DeliveryPoint({
    @RfControl(
      validators: const [RequiredValidator()],
    ) this.name = '',
    this.address,
  });
}

Animated list

Check the Animated URL list form sample to see how to deal with forms utilizing AnimatedList. There is a small trick which requires passing down the FormControl elements due to the fact that we need to provide same widget to animate removal while actual form item is missing in form.

reactive_forms_generator's People

Contributors

benjifarquhar avatar doug-l-mitchell avatar ethras avatar furaiev avatar ibrahim-mubarak avatar ipcjs avatar tortillazhawaii avatar vasilich6107 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

reactive_forms_generator's Issues

Annotation using material design

Hi,

When performing tests on Dart package (no UI only repository) I've this issue.

โœ“ Optimizing tests (4.2s)
package:reactive_forms_annotations has `uses-material-design: true` set but the primary pubspec contains `uses-material-design: false`. If the application needs material icons, then `uses-material-design`  must be set to true.

Could we change the pubspec.yaml to uses-material-design: false?
https://github.com/artflutter/reactive_forms_generator/blob/master/packages/reactive_forms_annotations/pubspec.yaml
and
https://github.com/artflutter/reactive_forms_generator/blob/master/packages/reactive_forms_generator/pubspec.yaml

I've checked the generator and seems no material icons used and must not be used in the future. Do you confirm?

Thank you for your feedback @vasilich6107

ExtendedControl's value doesn't get updated when updating the formgroup manually

I am working on an app that requires caching and restoring user input, and I have a problem when using FormArrayAnnotation is that it creates a separate typed field which isn't synced with the original FormArray control
e.g.:
https://github.com/artflutter/reactive_forms_generator/blob/master/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url.gform.dart#L188

when saving the form to Map<String,Object?> it works fine, but when restoring, I have to update the form using form.updateValue(cached); which doesn't update that separate field.

my suggestion is to remove this separate field, and instead infer its value from the current FormArray control

get Model error

I discover an error on the get model method.
If I use a class where not all the fields are mark as FormControlAnotation() the build runner use getters that does not exist.
Example:
class TinyForm{ @FormControlAnnotation() this.name, this.lastNameFather, }

Then the builder generates:

Tiny get model => Tiny( name: nameValue, => this exist lastNameFather: lastNameFatherValue, => this does not exist. The compiler does not warm before execution because the analyzer omits the files on the config recommended )

How to declare predefined validators?

I follow the doc to declare static method to get requiredValidator

Map<String, dynamic>? requiredValidator(AbstractControl<dynamic> control) {
  return Validators.required(control);
}

but I tried to implements also other methods
https://github.com/joanpablo/reactive_forms#predefined-validators

I've some lack of knowledge about declaring complex validators like min / max / equals / must match etc.
Do you think we could have those methods under the generated code? Otherwise could we have additional doc or resources in doc to implements those methods?

Thank you in advance for your help.

Not supporting analyzer 3

Could we upgrade to last version of analyzer on reactive_forms_generator to prevent breaking existing dependencies please?

Issue of example

Hi,
I run the same example of TinyForm and the output was:

Try removing the extra positional arguments.
                          e.ParameterElementImpl('FakeParameterElement', 20)
                                                ^
../../flutter/flutter/.pub-cache/hosted/pub.dartlang.org/analyzer-2.2.0/lib/src/dart/element/element.dart:4862:3: Context: Found this candidate, but the arguments don't match.
  ParameterElementImpl({
  ^^^^^^^^^^^^^^^^^^^^
[INFO] Precompiling build script... completed, took 1.1s

[SEVERE] Failed to precompile build script .dart_tool/build/entrypoint/build.dart.
This is likely caused by a misconfigured builder definition.

pub finished with exit code 78```

I'm running flutter 2.5.
I run build_runner clean and flutter clean but got the same error.
My Tiny Class is:
```@ReactiveFormAnnotation()
class Tiny {
  final String email;

  final String password;

  Tiny({
    @FormControlAnnotation() this.email = '',
    @FormControlAnnotation() this.password = '',
  });
}```

I'm running on the latest version of the generator.

Calling the nested FormGroups' "remove" functions does not remove the nested forms' controls

The code below does not enter inside the if statement (didn't use "contained" as that is another bug).

builder: (context, formModel, child) {
  formModel.homeForm.cityRemove();
  if (formModel.homeForm.cityControl == null) {
    print('You fixed removing a nested form\'s control. Good job!');
  }
}

Test it in the GroupRemoveFormWidget, which can be viewed in the app in the "Group remove test" menu item of the example app.

Validators set programmatically get cleared on didUpdateWidget

I have a non-const validator that will never be const because it gets passed an object which represents the current user of the app.

I have set the validator programmatically like this:

    formModel.form.setValidators([
      VpValidators.mustMatchIf(
        SettingsForm.emailAddressControlName,
        SettingsForm.confirmEmailAddressControlName,
        () => isEmailEdited(
          ref!.watch(userProvider).value!.emailAddress!,
          formModel.model,
        ),
      )
    ]);

But in didUpdateWidget, the line final elements = _formModel.formElements(); causes clearValidators() to run and clears all the validators and then my code which adds the vallidator programmatically does not re-run. Any ideas how I can either stop clearing the validators or re-run my setValidators code on didUpdateWidget?

Generated classes missing the resetValue member

Generated form model classes with @ReactiveFormAnnotation() producing missing implementation errors. The resetValue member of the FormModel<TModel> abstract class is ignored during code generation.

:Error: The non-abstract class 'TransferForm' is missing implementations for these members:
transfer_form_model.gform.dart:175
 - FormModel.resetValue
Try to either
 - provide an implementation,
 - inherit an implementation from a superclass or mixin,
 - mark the class as abstract, or
 - provide a 'noSuchMethod' implementation.

I noticed the 0.14.0-beta of reactive_forms_annotation does not include the resetValue member.

Make the auto-generated form class implement a minimal abstract class

Do you think you could make the auto-generated form implement a minimal abstract class since they all do anyway? They just don't have the implements keyword, which means we can't use the interface in our code:

abstract class FormModel<TModel> {
  FormModel({required this.form});
  final FormGroup form;
  TModel get model;
}

For example, I have this generated code:

class UserCredentialForm {

And would like it to generate this instead:

class UserCredentialForm implements FormModel<UserCredential> {

It would allow me to do this:


/// The base form view model which all other forms extend.
abstract class FormVm<TModel, TSubmitResponse> {
  FormVm({required this.$provider}) {
    $onInit();
  }

  @protected
  void $onInit() {}

  late FormModel<TModel> $formModel; // <===== see this
  late TModel $initialValue;

  @mustCallSuper
  void $onDestroy() {
    $formModel.form.markAsUntouched();// <===== here
  }
  
    Future<SuccessResult<TSubmitResponse>?> $saveMethod() async {
    return await ($formModel.model as DomainEntity).$save()// <===== here
        as Future<SuccessResult<TSubmitResponse>?>;
  }

How to get form value from `builder` instance?

I have the following widget, but I can't figure out how to get the value of the form.

The widget of concern is:

ElevatedButton(
                onPressed: () {
                  setState(() => form.model);
                },
                child: const Text('Save'),
              )

form.model is just a map with null values even though the form has defined fields; form.form is not defined, which is the example in the package.

Sub-request: Can you also address examples with Key key? Are we supposed to use GlobalKey<FormState>, that is what I have been doing, not sure if this is correct.

Whole widget:

class NewDatasetWidget extends StatefulWidget {
  const NewDatasetWidget({Key? key}) : super(key: key);

  @override
  NewDatasetWidgetState createState() => NewDatasetWidgetState();
}

class NewDatasetWidgetState extends State<NewDatasetWidget> {
  @override
  Widget build(BuildContext context) {
    final CreateDatasetFormBuilder form = CreateDatasetFormBuilder(
        key: context.read<UploadManifest>().newDatasetFormKey,
        model: CreateDataset(),
        builder: (BuildContext context, CreateDatasetForm formModel, Widget? child) {
          return Column(children: [
            ReactiveTextField<String>(
              formControl: formModel.labelControl,
              validationMessages: {
                ValidationMessage.required: (error) => "Must not be empty",
              },
              decoration: const InputDecoration(
                labelText: 'Label',
              ),
            ),
            columnSpacer,
            ReactiveTextField<String>(
              formControl: formModel.notesControl,
              validationMessages: {
                ValidationMessage.required: (error) => "Must not be empty",
              },
              decoration: const InputDecoration(
                labelText: 'Notes',
              ),
            ),
            columnSpacer,
            ReactiveTextField<String>(
              formControl: formModel.tagsForm.microscopyMethodForm.nameControl,
              validationMessages: {
                ValidationMessage.required: (error) => "Must not be empty",
              },
              decoration: const InputDecoration(
                labelText: 'Microscopy method',
              ),
            ),
            columnSpacer,
            ReactiveTextField<String>(
              formControl: formModel.tagsForm.animalForm.speciesControl,
              validationMessages: {
                ValidationMessage.required: (error) => "Must not be empty",
              },
              decoration: const InputDecoration(
                labelText: 'Animal Species',
              ),
            ),
            columnSpacer,
            ReactiveFormArray<Map<String, Object?>>(
              formArray: formModel.tagsForm.targetedMutationControl,
              builder: (context, formArray, child) => Column(children: [
                ...formArray.value!
                    .asMap()
                    .map((i, targetedMutation) => MapEntry(
                        i,
                        Card(
                            child: Padding(
                                padding: const EdgeInsets.symmetric(vertical: widgetSpacing),
                                child: ListTile(
                                    leading: const Icon(Icons.table_chart_rounded),
                                    title: Text("Gene ${i + 1}"),
                                    subtitle: Wrap(
                                      spacing: widgetSpacing,
                                      runSpacing: widgetSpacing,
                                      children: [
                                        ReactiveTextField<String>(
                                          formControlName: '$i.gene',
                                          validationMessages: {
                                            ValidationMessage.required: (error) =>
                                                "Must not be empty"
                                          },
                                          decoration: const InputDecoration(
                                            labelText: 'Gene name',
                                          ),
                                        ),
                                        ReactiveCheckboxListTile(
                                          formControlName: '$i.isKnockOut',
                                          title: const Text('Knock-out'),
                                          subtitle: const Text('Knock-in if left unchecked'),
                                        ),
                                        ReactiveTextField<String>(
                                          formControlName: '$i.method',
                                          validationMessages: {
                                            ValidationMessage.required: (error) =>
                                                "Must not be empty"
                                          },
                                          decoration: const InputDecoration(
                                            labelText: 'Method',
                                          ),
                                        )
                                      ],
                                    ),
                                    trailing: TextButton(
                                      child: const Text('Delete'),
                                      onPressed: () =>
                                          formModel.tagsForm.removeTargetedMutationItemAtIndex(i),
                                    ))))))
                    .values
                    .toList(),
                columnSpacer,
                ElevatedButton(
                  onPressed: () {
                    setState(() => formModel.tagsForm.addTargetedMutationItem(TargetedMutation()));
                  },
                  child: const Text('Add Targeted Mutation'),
                )
              ]),
            ),
          ]);
        });
    return Scaffold(
        primary: false,
        appBar: AppBar(
          title: const Text('New Dataset'),
        ),
        body: Padding(
            padding: const EdgeInsets.all(widgetSpacing),
            child: Column(children: [
              form,
              columnSpacer,
              ElevatedButton(
                onPressed: () {
                  setState(() => form.model);
                },
                child: const Text('Save'),
              )
            ])));
  }
}

This is my model:

import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

part 'forms.gform.dart';

Map<String, dynamic>? requiredValidator(AbstractControl<dynamic> control) {
  return Validators.required(control);
}

@ReactiveFormAnnotation()
class CreateProject {
  final String doi;
  final String? label;
  final String? notes;

  final List<String>? datasetIds;

  CreateProject(
      {@FormControlAnnotation(validators: [requiredValidator]) this.doi = '',
      @FormControlAnnotation() this.label,
      @FormControlAnnotation() this.notes,
      @FormControlAnnotation() this.datasetIds});
}

@ReactiveFormAnnotation()
class CreateDataset {
  final String? label;
  final String? notes;

  final DatasetTags? tags;

  CreateDataset(
      {@FormControlAnnotation() this.label, @FormControlAnnotation() this.notes, this.tags});
}

@FormGroupAnnotation()
class DatasetTags {
  final Animal? animal;
  final List<TargetedMutation> targetedMutation;
  final MicroscopyMethod? microscopyMethod;
  final BrainStructure? brainStructure;

  DatasetTags(
      {this.animal,
      @FormArrayAnnotation() this.targetedMutation = const [], // note diff
      this.microscopyMethod,
      this.brainStructure});
}

@FormGroupAnnotation()
class Animal {
  final String species;

  Animal({@FormControlAnnotation(validators: [requiredValidator]) this.species = ''});
}

@FormGroupAnnotation()
class TargetedMutation {
  final String gene;
  final bool isKnockOut;
  final String method;

  TargetedMutation(
      {@FormControlAnnotation(validators: [requiredValidator]) this.gene = '',
      @FormControlAnnotation() this.isKnockOut = false,
      @FormControlAnnotation(validators: [requiredValidator]) this.method = ''});
}

@FormGroupAnnotation()
class MicroscopyMethod {
  final String name;

  MicroscopyMethod({@FormControlAnnotation(validators: [requiredValidator]) this.name = ''});
}

class BrainStructure {
  final String name;

  BrainStructure({@FormControlAnnotation(validators: [requiredValidator]) this.name = ''});
}

[Enhance] Sintax Linter Correction

Widgets constructor should be const,

ReactiveMigrantFormConsumer({Key? key, required this.builder, this.child}) : super(key: key);

These 3 packages are not used by gform class:

import 'package:reactive_forms/src/widgets/inherited_streamer.dart'; import 'dart:convert'; import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

[proposal] Make generated file as a `part` of the base file

Currently, we need to import both base and generated files:

import 'login.dart';
import 'login.gform.dart';

Let's make it as in built_value or freezed use part / part of.

// base
part 'login.gform.dart';

// generated
part of 'login.dart';

In this case, we can import only the base file.

Provider / Riverpod plugin

The reactive_forms package says that reactive_forms can be used very well in combination with the provider plugin to separate the Ui logic and business logic. A FormGroup can be created within the business logic. Thus, the form can also be manipulated elegantly from the business logic at any time as desired.

Is there also the possibility to manipulate the form from the business logic when using reactive_forms_generator? Unfortunately I have not found a corresponding example.

FormArrayAnnotation validators have mismatching type

when generating a model with @FormArrayAnnotation<TModel> (not just @FormArrayAnnotation()) the validators assume it's a FormArray<TModel>, but the generated code is always FormArray<Map<String, Object?>>

e.g.:
if this was @FormArrayAnnotation<UrlEntity>(validators: [someValidator])
https://github.com/artflutter/reactive_forms_generator/blob/master/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url.dart#L10

then this would fail
https://github.com/artflutter/reactive_forms_generator/blob/master/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url.gform.dart#L444-L446

because the actual type is
https://github.com/artflutter/reactive_forms_generator/blob/master/packages/reactive_forms_generator/example/lib/docs/animated_url_list/url.gform.dart#L349-L350

New FormModel.reset method behaves slightly different to FormGroup equivalent

I wonder if it would be more intuitive to make formModel.reset() to reset the form to null, and allow the model to be passed in if we want the model to remain populated, like the current FormGroup.reset does. What do you think? If we want only one option, and no extra arguments, I would think that formModel.reset should just reset the form to null, as that is what I would expect it to do.

Form array error

Using latest version 0.3.
Flutter 2.5.1

The problems is when generating paths:

For creating forms:

migrantRecruitmentDataRecruitmentDataForm =
        (migrant.migrantRecruitmentData ?? [])
            .asMap()
            .map(
              (k, v) => MapEntry(
                k,
                RecruitmentDataForm(
                  recruitmentData: v,
                  form: form,
                  path: pathBuilder("migrantRecruitmentData.$k"),
                ),
              ),
            )
            .values
            .toList();

For retrieving:

List<RecruitmentData>? get migrantRecruitmentDataValue =>
      (migrantRecruitmentDataControl.value ?? [])
          .asMap()
          .map((k, v) => MapEntry(
              k,
              RecruitmentDataForm(
                      recruitmentData: RecruitmentData(),
                      form: form,
                      path: pathBuilder("deliveryList.$k"))
                  .model))
          .values
          .toList();

The path builder should be the same.
Also when generating the class name, it is repeated RecruitmentDataRecruitmentDataForm should be RecruitmentDataForm.

The casing of key names of Form value is different from what Freezed package generated

E.g:
final x = formModel.form.value

Result:
{ profileImage: "face.jpg" }

Desired Result:
{ profile_image: "face.jpg" }

This results from incompatibility of fromJson & toJson of Freezed Model

I already used @jsonkey(name: 'profile_image') and the result is still the same

Packages version:
freezed_annotation: ^2.0.3
json_annotation: ^4.6.0
reactive_forms: ^14.2.0
reactive_forms_annotations: ^1.0.0
build_runner: ^2.3.2
reactive_forms_generator: ^1.0.0
freezed: ^2.0.3
json_serializable: ^6.6.1

Custom getters with freezed throw errors with dartz

Hi,

Before starting, thank you for this awesome generator that prevent boilerplate to create forms! ๐Ÿ’ฏ

I'm using freezed to create my models and I've an issue when I'm adding a custom getter on freezed. The Reactive form annotation throw errors.

Here a sample:

import 'package:dartz/dartz.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

part 'test.freezed.dart';

@freezed
@ReactiveFormAnnotation()
class Test with _$Test {
  const Test._();
  const factory Test({
    @FormControlAnnotation() required String title,
    @FormControlAnnotation() String? description,
  }) = _Test;

  factory Test.empty() => Test(
        title: '',
        description: '',
      );
  // The error occurs with this grrr
  Option<String> get failureOption {
    if (title.isEmpty) {
      return some('Cannot be empty');
    } else {
      return none();
    }
  }
}

It's working fine without this getter.
Also, it's working with a simple getter

String get yolooo => 'Yoloo';

Option is from dartz module, the error occurs when importing on this freezed class.
The error occurred when instantiating the form in a widget.

final form = TestFormBuilder(
      model: Test.empty(),
      builder: (context, formModel, child) {
        return Column(children: [
          const SizedBox(height: 16),
          ReactiveTextField<String>(
            formControl: formModel.titleControl,
            validationMessages: (control) => {
              ValidationMessage.required: 'Required',
            },
            decoration: InputDecoration(labelText: 'Title'),
          ),
        ]);
      },
    );

Let me know if you need additional information.

Version used
dependencies
reactive_forms: ^10.6.4
reactive_forms_annotations: ^0.1.0-beta

dev dependencies
reactive_forms_generator: ^0.3.2-beta

Rerendering the FormBuilder with a new FormModel not visually updating TextField

Hi, I don't think there is a bug in the library, it is probably a bug in my code, I just wanted to see if anyone knows what could be going on here.

I am manually updating the FormModel of my widget, and the Textfields are not displaying the new value that is in the new FormModel. I can see that the FormModel is updated by inspecting this code with the debugger, which returns the widgets, but visually, the text field values do not update:

  @override
  List<Widget> detailsWidgets(WidgetRef wProvider, VgnItmLocalFormVm vm) {
    return [
      Expanded(
          child: DetailsFormBuilder(
              initState: (context, formModel) {
                vm.initState(context, formModel: formModel);
              },
              model: (vm as DetailsFormVm).formModel?.model ?? vm.initialValue,
              builder: (context, formModel, child) {
                vm.formModel = formModel;
                vm.initState(context, formModel: formModel);
                return Column(children: [
                  Expanded(
                      child: KeyboardActions(
                          tapOutsideBehavior: TapOutsideBehavior.opaqueDismiss,
                          keepFocusOnTappingNode: true,
                          config: KeyboardActionsConfig(
                            defaultDoneWidget: const VpKeyboardDoneButton(),
                            actions: [
                              KeyboardActionsItem(
                                focusNode: focusNodeName,
                              ),
                              KeyboardActionsItem(
                                  focusNode: focusNodeCompanyName,
                                  onTapAction: () {
                                    vm.formModel!.form.focus(
                                        DetailsForm.descriptionControlName);
                                  }),
                              KeyboardActionsItem(
                                  focusNode: focusNodeDescription,
                                  onTapAction: () {}),
                            ],
                          ),
                          child: Padding(
                              padding: formParentPadding(context),
                              child: Column(children: [
                                nameTextField(wProvider, vm),
                                const SizedBox(height: INPUT_VERTICAL_PAD),
                                companyNameTextField(vm),
                                const SizedBox(height: INPUT_VERTICAL_PAD),
                                descriptionTextField(context, vm),
                              ])))),
                  Visibility(
                      visible: manageVgnItmsPageVm.shouldDisplayFooter,
                      child: Center(child: vm.submitButton(context)))
                ]);
              }))
    ];
  }
  
    Widget descriptionTextField(BuildContext context, VgnItmLocalFormVm vm) {
    return VpTextField(
      maxLines: null,
      form: vm.formModel?.form,
      focusNode: focusNodeDescription,

      textInputType: TextInputType.multiline,
      textInputAction: TextInputAction.newline,
      // onSubmitted: (control) {
      //   shouldSubmitFromDescription ? detailsVm?.save(context) : null;
      // },
      maxLength: MAX_LENGTH_L,
      labelText: 'Description',
      formControlName: DetailsForm.descriptionControlName,
      hintText: descriptionHint,
    );
  }

If I manually completely remove the widget on the screen by clicking away, and then add it back in, it is now rendered with the correct updated value. Any ideas what is going on here?

generated FormBuilder should call `initState` on `didUpdateWidget`

example: https://github.com/artflutter/reactive_forms_generator/blob/master/generator_tests/test/doc/login_extended_test.dart#L275-L286

@override
void didUpdateWidget(covariant LoginExtendedFormBuilder oldWidget) {
  if (widget.model != oldWidget.model) {
    _formModel = LoginExtendedForm(
        widget.model, LoginExtendedForm.formElements(widget.model), null);
    if (_formModel.form.disabled) {
      _formModel.form.markAsDisabled();
    }
  }
  super.didUpdateWidget(oldWidget);
}

when this gets called, the _formModel is replaced with a new value, but it doesn't get passed to the initState parameter, this makes the developer unaware of changes to the formModel

proposed solution:

@override
void didUpdateWidget(covariant LoginExtendedFormBuilder oldWidget) {
  if (widget.model != oldWidget.model) {
    _formModel = LoginExtendedForm(
        widget.model, LoginExtendedForm.formElements(widget.model), null);
    if (_formModel.form.disabled) {
      _formModel.form.markAsDisabled();
    }
+    widget.initState?.call(context, _formModel);

  }
  super.didUpdateWidget(oldWidget);
}

Readme final example using wrong form model

The last example in the readme should use the FreezedClass model, not Tiny. As a result, the Freezed example is a bit less complete. I assume it is used the same way, however.

Cant add validators to freezed classes.

Cant seem to workout what to do to get it accept validators. It would be nice to have a more concrete example.

Example code and error

The values in a const list literal must be constants.
Try removing the keyword 'const' from the list literal.dartnon_constant_list_element
Arguments of a constant creation must be constant expressions.
Try making the argument a valid constant, or use 'new' to call the constructor.dartconst_with_non_constant_argument

/// [SignInFormModel] data class.
@Freezed()
@ReactiveFormAnnotation()
class SignInFormModel with _$SignInFormModel {
  SignInFormModel._();

  const factory SignInFormModel({
    @FormControlAnnotation(
      validators: [Validators.required],
    )
        String? email,
    @FormControlAnnotation()
        String? password,
  }) = _SignInFormModel;

  factory SignInFormModel.fromJson(Map<String, dynamic> json) => _$SignInFormModelFromJson(json);
}

Form to openapi generator `dart-dio` model

Hello how would you build the builder object, in my case CreateDataset, for openapi generator dart-dio from a the formModel.form.value. The keys match the model fields. I have the following:

  Future<void> upload(Map<String, Object?> formValue) async {
    Response<DatasetResponse> response =
        await syndbStargateApi.registerNewDatasetStargateNeurodataStargateStargateNewDatasetPost(
            createDataset: CreateDataset()); 
    Future.microtask(() => context.read<UploadManifest>().datasetId = response.data!.datasetId);
  }

Not sure how to fill CreateDataset(), I tried CreateDataset((b) => formValue), which didn't work. Not sure what to do instead

Usually I would do something like this:

UserCreate(((b) => b
                        ..email = data.name!
                        ..password = data.password!))

But this form is nested, so I can't do it this way. Get the following error:

Error: Tried to build class "CreateDataset" but nested builder for field "tags" threw: Tried to build class "DatasetTags" but nested builder for field "animal" threw: Tried to construct class "SyndbApiNeurodataModelsAnimalLeaf" with null field "species". This is forbidden; to allow it, mark "species" with @nullable.

Which is not true:
image

The static formControlNames could be const

Pretty minor but I noticed the generated formcontrol names e.g.

class SettingsForm implements FormModel<Settings> {
  SettingsForm(
    this.settings,
    this.form,
    this.path,
  ) {}

  static String emailAddressControlName = "emailAddress"; <============================= this

are not const and could be.

It would make them usable in, for example, the mustMatch validator in the example project:

  Map<String, dynamic>? mustMatch(AbstractControl<dynamic> control) {
  const email = 'email';
  const password = 'password';

  final form = control as FormGroup;

  final formControl = form.control(email);
  final matchingFormControl = form.control(password);

  if (formControl.value != matchingFormControl.value) {
    matchingFormControl.setErrors(<String, dynamic>{
      ...matchingFormControl.errors,
      ...<String, dynamic>{'mustMatch': true},
    });

    // force messages to show up as soon as possible
    matchingFormControl.markAsTouched();
  } else {
    matchingFormControl.removeError('mustMatch');
  }

  return null;
}

Actually I have just realised they are already usable in the above code example, just removing the const key work, so not sure if it is worth it.

User profile form not running

I was running the example code. But when I click User profile section , received following error.
Screenshot at 2022-10-16 15-29-08

I am running flutter version 3.3.4.

I found that the error is because of the address type.

Error:-

  โ•โ•โ•ก EXCEPTION CAUGHT BY WIDGETS LIBRARY โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
                     The following assertion was thrown building ReactiveFormBuilder(dirty, state:
                     ReactiveFormBuilderState#4795d):
                     Must provide a formControlName or a formControl, but not both at the same time.
                     'package:reactive_forms/src/widgets/reactive_form_field.dart':
                     Failed assertion: line 73 pos 13: '(formControlName != null && formControl == null) ||
                                     (formControlName == null && formControl != null)'
                     
                     The relevant error-causing widget was:
                       ReactiveFormBuilder

ReactiveFormBuilder:file:///home/sumit/AndroidProjects/reactive_forms_generator/packages/reactive_forms_generator/example/lib/docs
                       /user_profile/user_profile.gform.dart:157:14
                     
                     When the exception was thrown, this was the stack:
                     #2      new ReactiveFormField (package:reactive_forms/src/widgets/reactive_form_field.dart:73:13)
                     #3      new ReactiveTextField (package:reactive_forms/src/widgets/reactive_text_field.dart:145:9)
                     #4      UserProfileFormWidget.build.<anonymous closure>
(package:example/docs/user_profile/user_profile_form.dart:57:19)
                     #5      _UserProfileFormBuilderState.build.<anonymous closure>
(package:example/docs/user_profile/user_profile.gform.dart:161:27)
                     #6      ReactiveFormBuilderState.build (package:reactive_forms/src/widgets/reactive_form_builder.dart:85:28)
                     #7      StatefulElement.build (package:flutter/src/widgets/framework.dart:4992:27)
                     #8      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4878:15)
                     #9      StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5050:11)
                     #10     Element.rebuild (package:flutter/src/widgets/framework.dart:4604:5)
                     #11     ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4859:5)
                     #12     StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5041:11)
                     #13     ComponentElement.mount (package:flutter/src/widgets/framework.dart:4853:5)
                     ...     Normal element mounting (170 frames)
                     #183    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3863:16)
                     #184    MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:6435:36)
                     #185    MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6447:32)
                     ...     Normal element mounting (391 frames)
                     #576    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3863:16)
                     #577    MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:6435:36)
                     #578    Element.updateChild (package:flutter/src/widgets/framework.dart:3592:18)
                     #579    RenderObjectElement.updateChildren (package:flutter/src/widgets/framework.dart:5964:32)
                     #580    MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6460:17)
                     #581    Element.updateChild (package:flutter/src/widgets/framework.dart:3570:15)
                     #582    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4904:16)
                     #583    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5050:11)
                     #584    Element.rebuild (package:flutter/src/widgets/framework.dart:4604:5)
                     #585    StatefulElement.update (package:flutter/src/widgets/framework.dart:5082:5)
                     #586    Element.updateChild (package:flutter/src/widgets/framework.dart:3570:15)
                     #587    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4904:16)
                     #588    Element.rebuild (package:flutter/src/widgets/framework.dart:4604:5)
                     #589    ProxyElement.update (package:flutter/src/widgets/framework.dart:5228:5)
                     #590    Element.updateChild (package:flutter/src/widgets/framework.dart:3570:15)
                     #591    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4904:16)
                     #592    Element.rebuild (package:flutter/src/widgets/framework.dart:4604:5)
                     #593    ProxyElement.update (package:flutter/src/widgets/framework.dart:5228:5)
                     #594    _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:107:11)
                     #595    Element.updateChild (package:flutter/src/widgets/framework.dart:3570:15)
                     #596    SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6307:14)
                     #597    Element.updateChild (package:flutter/src/widgets/framework.dart:3570:15)
                     #598    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4904:16)
                     #599    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5050:11)
                     #600    Element.rebuild (package:flutter/src/widgets/framework.dart:4604:5)
                     #601    StatefulElement.update (package:flutter/src/widgets/framework.dart:5082:5)
                     #602    Element.updateChild (package:flutter/src/widgets/framework.dart:3570:15)
                     #603    SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6307:14)
                     #604    Element.updateChild (package:flutter/src/widgets/framework.dart:3570:15)
                     #605    SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6307:14)
                     #606    Element.updateChild (package:flutter/src/widgets/framework.dart:3570:15)
                     #607    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4904:16)
                     #608    Element.rebuild (package:flutter/src/widgets/framework.dart:4604:5)
                     #609    ProxyElement.update (package:flutter/src/widgets/framework.dart:5228:5)
                     #610    Element.updateChild (package:flutter/src/widgets/framework.dart:3570:15)
                     #611    ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4904:16)
                     #612    StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5050:11)
                     #613    Element.rebuild (package:flutter/src/widgets/framework.dart:4604:5)
                     #614    BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2667:19)
                     #615    WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:882:21)
                     #616    RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:378:5)
                     #617    SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1175:15)
                     #618    SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1104:9)
                     #619    SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1015:5)
                     #620    _invoke (dart:ui/hooks.dart:148:13)
                     #621    PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:318:5)
                     #622    _drawFrame (dart:ui/hooks.dart:115:31)
                     (elided 2 frames from class _AssertionError)
                     
                     โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
                     ```

Support Freezed classes

Hello,

I'll start by saying I love the idea of the package. However, I use Freezed to generate my data classes.
I tried adding the generator to a freezed class but the properties of factory constructors don't seem to get picked up.

As an example here is the freezed class:

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

part 'test.g.dart';
part 'test.freezed.dart';

@freezed
@ReactiveFormAnnotation()
class Test with _$Test {
  const factory Test({
    @FormControlAnnotation() String? id,
    @FormControlAnnotation() String? name,
    @FormControlAnnotation() double? year,
  }) = _Test;

  factory Test.fromJson(Map<String, dynamic> json) => _$TestFromJson(json);
}

The expected behaviour would be something like:

class TestForm {
  TestForm(this.test, this.form, this.path) {}

  final Test test;

  final FormGroup form;

  final String? path;

  Test get model => Test();
  FormGroup formElements() => FormGroup({
        'id': FormControl<String>(),
        'name': FormControl<String>(),
        'year': FormControl<double>(),
      },
          validators: [],
          asyncValidators: [],
          asyncValidatorsDebounceTime: 250,
          disabled: false);
}

Would you consider adding support for this in the near future ?

Remove() functions cause FormControlNotFoundException when accessing FormModel.model

For the scenario where you want to re-use a form model but without some fields, we have the generated remove functions, e.g.:

  void passwordRemove({bool updateParent = true, bool emitEvent = true}) =>
      form.removeControl(passwordControlPath(),
          updateParent: updateParent, emitEvent: emitEvent);

When you use them:

  void setUpForm() {
    formModel.passwordRemove();
  }

Then try to access formModel.model, you get a FormControlNotFoundException.

The reactive form model class cannot have a constructor with non-nullable fields

When the generated code constructs the model class it uses null propagation to get the corresponding property from the original model instance, thereby attempting to pass in a nullable version of the expected property type.

An example of where this issue can be seen is in login_extended. It's only not an issue in this instance because LoginExtended.unAnnotated is nullable, but if it's changed to non-nullable it would cause a compile time error.

The one example I've seen where it set the model as nullable, and therefore doesn't require null propagation when accessing it's properties, is in profile, but I don't know how to replicate.

Example of a ReactiveFormsGenerator on ViewModel

I have this form class (generated by ReactiveFormsGenerator) called ReactiveProductForm,

can you please provide an example where ReactiveProductForm is placed on ViewModel and being called on View?

I already check the docs, particulary the
add_dynamic_controls_sample.dart

but it only shows generic reactive form :(

It would be very helpful if you can provide an example, Thanks!

support for generics?

Does this package have support for generic nested forms?

Following your example:

class DeliveryList {
  final List<DeliveryPoint> deliveryList;

  DeliveryList({
    this.deliveryList = const [],
  });
}

abstract class DeliveryPoint {
  final String name;

  final Address? address;

  DeliveryPoint({
    this.name = '',
    this.address,
  });
}

class PrivateDeliveryPoint extends DeliveryPoint{
final String personName;
..
}

class CompanyDeliveryPoint extends DeliveryPoint(
final String partnerId;
..
}

I really like the features so far but I'm stuck here.

Thanks for the help!

Generic Form

Is it possible to add a generic type to a form?

Like:

part 'tags_form_definition.freezed.dart';
part 'tags_form_definition.gform.dart';

@freezed
@ReactiveFormAnnotation()
class Tags<T> with _$Tags<T> {
  factory Tags({
    @FormControlAnnotation(
      validators: [VpValidators.optionSelected],
    )
        required List<T>? tags,
  }) = _Tags;

  const Tags._();
}

I'm getting a cast error here:

      groceryItm: (groceryItm) => groceryItm.copyWith(
          tags: (form as TagsForm).model.tags as List<GroceryItmTag>?,

Because it is actually List<dynamic> due to:

part 'tags_form_definition.freezed.dart';
part 'tags_form_definition.gform.dart';

@freezed
@ReactiveFormAnnotation()
class Tags with _$Tags {
  factory Tags({
    @FormControlAnnotation(
      validators: [VpValidators.optionSelected],
    )
        required List? tags,
  }) = _Tags;

  const Tags._();
}

As the form is reused with List<GroceryItmTag>, List<MenuItmTag>, and List<FashionItmTag>

Even though the concrete value is List<GroceryItmTag>, it cannot be assigned to a field of type List<GroceryItemTag>:

Screenshot 2022-09-26 at 8 13 13 PM

How to use non-const validators

What's the best way to use a validator that takes an argument like a min or max?

the validators in the .gform file force it to be a const, so this has a compile time error:

      @FormControlAnnotation(
        validators: [VpValidators.minDuration(Duration(seconds: 10))],
      )

Setting focus to a control programmatically doesn't work unless you physically focus a field first.

Paste this code into packages/reactive_forms_generator/example/lib/docs/login/login_form.dart and run it, then go to the "Login Form". Then without clicking on a form control, hit the "Submit" button. It should focus the password control but doesn't.

Note that if you click on a field first, it will begin to work.

import 'package:example/docs/login/login.dart';
import 'package:example/sample_screen.dart';
import 'package:flutter/material.dart' hide ProgressIndicator;
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

class LoginFormWidget extends StatelessWidget {
  const LoginFormWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SampleScreen(
      title: const Text('Login'),
      body: BasicFormBuilder(
        initState: (context, formModel) {
          formModel.passwordFocus;
          formModel.form.focus('password');
        },
        builder: (context, formModel, child) {
          return Column(
            children: [
              ReactiveTextField<String>(
                formControl: formModel.emailControl,
                validationMessages: {
                  ValidationMessage.required: (_) => 'Required'
                },
                showErrors: (_) => false,
                decoration: const InputDecoration(
                  labelText: 'Email',
                  helperText: '',
                  helperStyle: TextStyle(height: 0.7),
                  errorStyle: TextStyle(height: 0.7),
                ),
              ),
              const SizedBox(height: 8.0),
              ReactiveTextField<String>(
                formControl: formModel.passwordControl,
                obscureText: true,
                showErrors: (_) => false,
                validationMessages: {
                  ValidationMessage.required: (_) => 'Required'
                },
                textInputAction: TextInputAction.done,
                decoration: const InputDecoration(
                  labelText: 'Password',
                  helperText: '',
                  helperStyle: TextStyle(height: 0.7),
                  errorStyle: TextStyle(height: 0.7),
                ),
              ),
              ReactiveBasicFormConsumer(
                builder: (context, formModel, child) {
                  // debugPrint(formModel.passwordControl.errors);
                  // debugPrint(formModel.form);
                  debugPrint('dirty => ${formModel.form.dirty}');
                  debugPrint(
                      'passwordDirty => ${formModel.passwordControl.dirty}');

                  return Text(formModel.emailControl.errors.toString());
                },
              ),
              const SizedBox(height: 8.0),
              ReactiveBasicFormConsumer(
                builder: (context, formModel, child) {
                  return ElevatedButton(
                    onPressed: () {
                      formModel.passwordFocus;
                    },
                    child: const Text('Submit'),
                  );
                },
              ),
            ],
          );
        },
      ),
    );
  }
}

Nested FormGroups "contains" functions return false when they should return true

The code below returns false when the homeForm does contain a control called "city".

builder: (context, formModel, child) {
  if (formModel.homeForm.containsCity) {
    print('Issue is fixed, well done!');
  }
}

Test it in the GroupContainsFormWidget, which can be viewed in the app in the "Group contains test" menu item of the example app.

Duplicates in generated code

Had random duplicates of methods throughout the entire generated code. Removing the duplicates hopefully solves the issue, will test tomorrow. Had ~20 duplicate methods.

import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

part 'forms.gform.dart';

Map<String, dynamic>? requiredValidator(AbstractControl<dynamic> control) {
  return Validators.required(control);
}

@ReactiveFormAnnotation()
class CreateProject {
  final String doi;
  final String? label;
  final String? notes;

  final List<String>? datasetIds;

  CreateProject(
      {@FormControlAnnotation(validators: [requiredValidator]) this.doi = '',
      @FormControlAnnotation() this.label,
      @FormControlAnnotation() this.notes,
      @FormControlAnnotation() this.datasetIds});
}

@ReactiveFormAnnotation()
class CreateDataset {
  final String? label;
  final String? notes;

  final Animal? animal;
  final List<TargetedMutation> targetedMutation;
  final MicroscopyMethod? microscopyMethod;
  final BrainStructure? brainStructure;

  CreateDataset(
      {@FormControlAnnotation() this.label,
      @FormControlAnnotation() this.notes,
      @FormControlAnnotation() this.animal,
      @FormArrayAnnotation() this.targetedMutation = const [], // note diff
      @FormControlAnnotation() this.microscopyMethod,
      @FormControlAnnotation() this.brainStructure});
}

@FormGroupAnnotation()
class Animal {
  final String species;

  Animal({@FormControlAnnotation(validators: [requiredValidator]) this.species = ''});
}

@FormGroupAnnotation()
class TargetedMutation {
  final String gene;
  final bool isKnockOut;
  final String method;

  TargetedMutation(
      {@FormControlAnnotation(validators: [requiredValidator]) this.gene = '',
      @FormControlAnnotation() this.isKnockOut = false,
      @FormControlAnnotation(validators: [requiredValidator]) this.method = ''});
}

@FormGroupAnnotation()
class MicroscopyMethod {
  final String name;

  MicroscopyMethod({@FormControlAnnotation(validators: [requiredValidator]) this.name = ''});
}

class BrainStructure {
  final String name;

  BrainStructure({@FormControlAnnotation(validators: [requiredValidator]) this.name = ''});
}

Excerpt from generator:

  String labelControlPath() => pathBuilder(labelControlName);
  String notesControlPath() => pathBuilder(notesControlName);
  String animalControlPath() => pathBuilder(animalControlName);
  String microscopyMethodControlPath() =>
      pathBuilder(microscopyMethodControlName);
  String brainStructureControlPath() => pathBuilder(brainStructureControlName);
  String animalControlPath() => pathBuilder(animalControlName);
  String microscopyMethodControlPath() =>
      pathBuilder(microscopyMethodControlName);
  Object? get labelErrors => labelControl?.errors;
  Object? get notesErrors => notesControl?.errors;
  Object? get animalErrors => animalControl?.errors;
  Object? get microscopyMethodErrors => microscopyMethodControl?.errors;
  Object? get brainStructureErrors => brainStructureControl?.errors;
  Object? get animalErrors => animalControl?.errors;
  Object? get microscopyMethodErrors => microscopyMethodControl?.errors;
  Object? get targetedMutationErrors => targetedMutationControl.errors;
  void get labelFocus => form.focus(labelControlPath());
  void get notesFocus => form.focus(notesControlPath());
  void get animalFocus => form.focus(animalControlPath());
  void get microscopyMethodFocus => form.focus(microscopyMethodControlPath());
  void get brainStructureFocus => form.focus(brainStructureControlPath());
  void get animalFocus => form.focus(animalControlPath());
  void get microscopyMethodFocus => form.focus(microscopyMethodControlPath());
  void get targetedMutationFocus => form.focus(targetedMutationControlPath());

How to populate the form from some business logic/repository pattern?

Hello

I need to populate the form from Cubit/Repository classes in order to edit/update certain data in the repository.

is there some annotations to indicate initial State and to integrate business logic (BlocBuilder in this case) in the form?
is there some similar example in some link?

Thanks in advance
Regards

Shared models are generated twice

consider this code:
data.dart:

part 'data.gform.dart';
@FormGroupAnnotation()
class DataDefModel {
  final String? id;
  final String name;

  const DataDefModel({
    @FormControlAnnotation() required this.id,
    @FormControlAnnotation() required this.name,
  });
}

a.dart:

import 'data.dart';
part 'a.gform.dart';

@ReactiveFormAnnotation()
@FormGroupAnnotation()
class AModel {
  final List<DataDefModel> dataDefs;

  const AModel({
    @FormArrayAnnotation() required this.dataDefs,
  });
}

b.dart:

import 'data.dart';
part 'b.gform.dart';

@ReactiveFormAnnotation()
@FormGroupAnnotation()
class BModel {
  final List<DataDefModel> dataDefs;

  const BModel({
    @FormArrayAnnotation() required this.dataDefs,
  });
}

this will generate DataDefModelForm 3 times (in data.gform.dart, a.gform.dart and b.gform.dart) which will lead to 3 different versions of the same class
cc: @vasilich6107

0.23.2 causing an infinite loop

0.23.2 is causing my widgets to rebuild in an infinite loop.

I'm very perplexed by this issue as 0.23.1 works fine. When I deleted didUpdateWidget on 0.23.2 it works fine. I have to hardcode my version to 0.23.1 for now. I will try to reproduce when I have time. Leaving this here in case anyone has any ideas what it could be.

Bump reactive_forms

I assume this is on the roadmap to bump to reactive_forms 14. It is quite early still, my bad ๐Ÿ˜„

Freezed: Generated *.gform.dart contains errors (Generated duplicated code)

Using Freezed. The generated model.gform.dart file contains errors. It generated duplicate code.

Files: GitHub Gist

Here's errors when run:

Terminal

lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:200:10: Error: 'jobControlPath' is already declared in this scope.
  String jobControlPath() => pathBuilder(jobControlName);
         ^^^^^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:197:10: Context: Previous declaration of 'jobControlPath'.
  String jobControlPath() => pathBuilder(jobControlName);
         ^^^^^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:204:35: Error: '_jobValue' is already declared in this scope.
  InboundOrderNewFormModelJob get _jobValue => jobForm.model;
                                  ^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:201:35: Context: Previous declaration of '_jobValue'.
  InboundOrderNewFormModelJob get _jobValue => jobForm.model;
                                  ^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:232:12: Error: 'containsJob' is already declared in this scope.
  bool get containsJob {
           ^^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:205:12: Context: Previous declaration of 'containsJob'.
  bool get containsJob {
           ^^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:244:15: Error: 'jobErrors' is already declared in this scope.
  Object? get jobErrors => jobControl.errors;
              ^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:241:15: Context: Previous declaration of 'jobErrors'.
  Object? get jobErrors => jobControl.errors;
              ^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:248:12: Error: 'jobFocus' is already declared in this scope.
  void get jobFocus => form.focus(jobControlPath());
           ^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:245:12: Context: Previous declaration of 'jobFocus'.
  void get jobFocus => form.focus(jobControlPath());
           ^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:280:8: Error: 'jobValueUpdate' is already declared in this scope.
  void jobValueUpdate(
       ^^^^^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:249:8: Context: Previous declaration of 'jobValueUpdate'.
  void jobValueUpdate(
       ^^^^^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:324:8: Error: 'jobValuePatch' is already declared in this scope.
  void jobValuePatch(
       ^^^^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:293:8: Context: Previous declaration of 'jobValuePatch'.
  void jobValuePatch(
       ^^^^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:368:8: Error: 'jobValueReset' is already declared in this scope.
  void jobValueReset(
       ^^^^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:337:8: Context: Previous declaration of 'jobValueReset'.
  void jobValueReset(
       ^^^^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:386:17: Error: 'jobControl' is already declared in this scope.
  FormGroup get jobControl => form.control(jobControlPath()) as FormGroup;
                ^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:381:17: Context: Previous declaration of 'jobControl'.
  FormGroup get jobControl => form.control(jobControlPath()) as FormGroup;
                ^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:441:8: Error: 'jobSetDisabled' is already declared in this scope.
  void jobSetDisabled(
       ^^^^^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:387:8: Context: Previous declaration of 'jobSetDisabled'.
  void jobSetDisabled(
       ^^^^^^^^^^^^^^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:207:20: Error: Can't use 'jobControlPath' because it is declared more than once.
      form.control(jobControlPath());
                   ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:234:20: Error: Can't use 'jobControlPath' because it is declared more than once.
      form.control(jobControlPath());
                   ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:241:28: Error: Can't use 'jobControl' because it is declared more than once.
  Object? get jobErrors => jobControl.errors;
                           ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:244:28: Error: Can't use 'jobControl' because it is declared more than once.
  Object? get jobErrors => jobControl.errors;
                           ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:245:35: Error: Can't use 'jobControlPath' because it is declared more than once.
  void get jobFocus => form.focus(jobControlPath());
                                  ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:248:35: Error: Can't use 'jobControlPath' because it is declared more than once.
  void get jobFocus => form.focus(jobControlPath());
                                  ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:254:5: Error: Can't use 'jobControl' because it is declared more than once.
    jobControl.updateValue(
    ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:285:5: Error: Can't use 'jobControl' because it is declared more than once.
    jobControl.updateValue(
    ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:298:5: Error: Can't use 'jobControl' because it is declared more than once.
    jobControl.updateValue(
    ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:329:5: Error: Can't use 'jobControl' because it is declared more than once.
    jobControl.updateValue(
    ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:344:7: Error: Can't use 'jobControl' because it is declared more than once.
      jobControl.reset(
      ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:375:7: Error: Can't use 'jobControl' because it is declared more than once.
      jobControl.reset(
      ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:381:44: Error: Can't use 'jobControlPath' because it is declared more than once.
  FormGroup get jobControl => form.control(jobControlPath()) as FormGroup;
                                           ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:386:44: Error: Can't use 'jobControlPath' because it is declared more than once.
  FormGroup get jobControl => form.control(jobControlPath()) as FormGroup;
                                           ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:393:7: Error: Can't use 'jobControl' because it is declared more than once.
      jobControl.markAsDisabled(
      ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:398:7: Error: Can't use 'jobControl' because it is declared more than once.
      jobControl.markAsEnabled(
      ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:447:7: Error: Can't use 'jobControl' because it is declared more than once.
      jobControl.markAsDisabled(
      ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:452:7: Error: Can't use 'jobControl' because it is declared more than once.
      jobControl.markAsEnabled(
      ^
lib/src/features/home/inbound/order/new/domain/form/model.gform.dart:466:14: Error: Can't use '_jobValue' because it is declared more than once.
        job: _jobValue, importEntryNo: _importEntryNoValue, blNo: _blNoValue);
             ^

reactive_forms_annotations and reactive_forms_generator are pretty stable

Hi @vasilich6107

This repos is pretty stable, used more than 1 year now :) thank you for this great job!

Are we able to remove the -beta tag? To let us to use the convention of dart numbering and allow us to add rules like constraints resolution and use tools like show dependency version to get an overview of which version we need to upgrade

image

Source: https://dart.dev/tools/pub/versioning

Thank you for your feedback and your action on this topic!

Applicable to reactive_forms_annotations and reactive_forms_generator

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.