artflutter / reactive_forms_generator

Other
89 stars 22 forks source link

Custom getters with freezed throw errors with dartz #12

Closed stact closed 2 years ago

stact commented 2 years ago

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

vasilich6107 commented 2 years ago

Hi @stact Will check

stact commented 2 years ago

@vasilich6107 I've removed manually import from generated file, no errors. Is this bug related to issue #8 ?

Also related to #10

vasilich6107 commented 2 years ago

@stact could you add an error description which you have?

stact commented 2 years ago
- '_TestFormBuilderState' is from 'package:app/domain/groups/test.gform.dart' ('lib/domain/groups/test.gform.dart').
package:app/…/groups/test.gform.dart:1
Try correcting the name to the name of an existing getter, or defining a getter or field named 'widget'.
        child: widget.builder(context, _formModel, widget.child),
               ^^^^^^

: Error: The getter 'widget' isn't defined for the class '_TestFormBuilderState'.
package:app/…/groups/test.gform.dart:131
- '_TestFormBuilderState' is from 'package:app/domain/groups/test.gform.dart' ('lib/domain/groups/test.gform.dart').
package:app/…/groups/test.gform.dart:1
Try correcting the name to the name of an existing getter, or defining a getter or field named 'widget'.
        child: widget.builder(context, _formModel, widget.child),

                                                   ^^^^^^
stact commented 2 years ago

// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// ReactiveFormsGenerator
// **************************************************************************

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:reactive_forms/reactive_forms.dart';
import 'package:reactive_forms/src/widgets/inherited_streamer.dart';
import 'package:dartz/dartz.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';
import 'dart:core';
part 'test.dart';

class ReactiveTestFormConsumer extends StatelessWidget {
  ReactiveTestFormConsumer({Key? key, required this.builder, this.child}) : super(key: key);

  final Widget? child;

  final Widget Function(BuildContext context, TestForm formModel, Widget? child) builder;

  @override
  Widget build(BuildContext context) {
    final formModel = ReactiveTestForm.of(context);

    if (formModel is! TestForm) {
      throw FormControlParentNotFoundException(this);
    }
    return builder(context, formModel, child);
  }
}

class TestFormInheritedStreamer extends InheritedStreamer<dynamic> {
  TestFormInheritedStreamer({Key? key, required this.form, required Stream<dynamic> stream, required Widget child}) : super(stream, child, key: key);

  final TestForm form;
}

class ReactiveTestForm extends StatelessWidget {
  ReactiveTestForm({Key? key, required this.form, required this.child, this.onWillPop}) : super(key: key);

  final Widget child;

  final TestForm form;

  final WillPopCallback? onWillPop;

  static TestForm? of(BuildContext context, {bool listen = true}) {
    if (listen) {
      return context.dependOnInheritedWidgetOfExactType<TestFormInheritedStreamer>()?.form;
    }

    final element = context.getElementForInheritedWidgetOfExactType<TestFormInheritedStreamer>();
    return element == null ? null : (element.widget as TestFormInheritedStreamer).form;
  }

  @override
  Widget build(BuildContext context) {
    return TestFormInheritedStreamer(
      form: form,
      stream: form.form.statusChanged,
      child: WillPopScope(
        onWillPop: onWillPop,
        child: child,
      ),
    );
  }
}

class TestFormBuilder extends StatefulWidget {
  TestFormBuilder({Key? key, required this.model, this.child, this.onWillPop, required this.builder}) : super(key: key);

  final Test model;

  final Widget? child;

  final WillPopCallback? onWillPop;

  final Widget Function(BuildContext context, TestForm formModel, Widget? child) builder;

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

class _TestFormBuilderState extends State<TestFormBuilder> {
  late FormGroup _form;

  late TestForm _formModel;

  @override
  void initState() {
    _form = FormGroup({});
    _formModel = TestForm(widget.model, _form, null);

    _form.addAll(_formModel.formElements().controls);

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ReactiveTestForm(
      form: _formModel,
      onWillPop: widget.onWillPop,
      child: ReactiveForm(
        formGroup: _form,
        onWillPop: widget.onWillPop,
        child: widget.builder(context, _formModel, widget.child),
      ),
    );
  }
}

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

  static String titleControlName = "title";

  final Test test;

  final FormGroup form;

  final String? path;

  String titleControlPath() => pathBuilder(titleControlName);
  String get titleValue => titleControl.value as String;
  bool get containsTitle => form.contains(titleControlPath());
  Object? get titleErrors => titleControl.errors;
  void get titleFocus => form.focus(titleControlPath());
  void titleRemove({bool updateParent = true, bool emitEvent = true}) =>
      form.removeControl(titleControlPath(), updateParent: updateParent, emitEvent: emitEvent);
  void titleValueUpdate(String value, {bool updateParent = true, bool emitEvent = true}) =>
      titleControl.updateValue(value, updateParent: updateParent, emitEvent: emitEvent);
  void titleValuePatch(String value, {bool updateParent = true, bool emitEvent = true}) =>
      titleControl.patchValue(value, updateParent: updateParent, emitEvent: emitEvent);
  void titleValueReset(String value, {bool updateParent = true, bool emitEvent = true, bool removeFocus = false, bool? disabled}) =>
      titleControl.reset(value: value, updateParent: updateParent, emitEvent: emitEvent);
  FormControl<String> get titleControl => form.control(titleControlPath()) as FormControl<String>;
  Test get model => Test(title: titleValue, description: test.description);
  void updateValue(Test value, {bool updateParent = true, bool emitEvent = true}) =>
      form.updateValue(TestForm(value, FormGroup({}), null).formElements().rawValue, updateParent: updateParent, emitEvent: emitEvent);
  void resetValue(Test value, {bool updateParent = true, bool emitEvent = true}) =>
      form.reset(value: TestForm(value, FormGroup({}), null).formElements().rawValue, updateParent: updateParent, emitEvent: emitEvent);
  void reset({bool updateParent = true, bool emitEvent = true}) =>
      form.reset(value: this.formElements().rawValue, updateParent: updateParent, emitEvent: emitEvent);
  String pathBuilder(String? pathItem) => [path, pathItem].whereType<String>().join(".");
  FormGroup formElements() => FormGroup({
        titleControlName: FormControl<String>(
            value: test.title, validators: [], asyncValidators: [], asyncValidatorsDebounceTime: 250, disabled: false, touched: false)
      }, validators: [], asyncValidators: [], asyncValidatorsDebounceTime: 250, disabled: false);
}
stact commented 2 years ago

@vasilich6107 got something better:

image
vasilich6107 commented 2 years ago

@stact wait for the next release

you'll have change your model file slightly

import 'package:flutter/material.dart';
import 'package:reactive_forms/reactive_forms.dart';
import 'package:reactive_forms/src/widgets/inherited_streamer.dart';
import 'package:dartz/dartz.dart' as dartz;
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

part 'test.freezed.dart';
part 'test.gform.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
  dartz.Option<String> get failureOption {
    if (title.isEmpty) {
      return dartz.some('Cannot be empty');
    } else {
      return dartz.none();
    }
  }
}
stact commented 2 years ago

Yes but without part 'test.gform.dart'; ^^) because it's the future of Reactive annotation! Thank you @vasilich6107

stact commented 2 years ago

Yup @vasilich6107 the problem is still here with as dartz. It seems that the generated code is ignoring this keyword.

vasilich6107 commented 2 years ago

@stact you did not pay attention to my message.

the main part of it was “wait for the next release”

stact commented 2 years ago

I thought that was a workaround. On the generated code we have unnecessary imports (not used). Maybe the solution must be have part of the file generated like freezed.

// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target

part of 'test.dart';

// **************************************************************************
// FreezedGenerator
// **************************************************************************
[...]
vasilich6107 commented 2 years ago

I’m working on fix Will release today

stact commented 2 years ago

May the force be with you 💪

vasilich6107 commented 2 years ago

@stact Fixed in 0.4.0-beta Check the changelog https://github.com/artflutter/reactive_forms_generator/blob/master/packages/reactive_forms_generator/CHANGELOG.md

stact commented 2 years ago

Done, it's working.

Unfortunately, I've some confusion to integrate additional imports that not used in the file directly.

import 'package:flutter/material.dart';
import 'package:reactive_forms/reactive_forms.dart';
import 'package:reactive_forms/src/widgets/inherited_streamer.dart';

Those imports could be in reactive_forms_annotations.dart instead? Refs: https://github.com/rrousselGit/freezed/blob/master/packages/freezed_annotation/lib/freezed_annotation.dart https://stackoverflow.com/questions/55579092/how-to-avoid-writing-an-import-for-every-single-file-in-dart-flutter

Hope you will plan to improve this annotation to just import

import 'package:reactive_forms_annotations/reactive_forms_annotations.dart';

Additionally to avoid this kind of warning:

image
vasilich6107 commented 2 years ago

Unfortunately, I've some confusion to integrate additional imports that not used in the file directly.

This is how parts work. You cannot import anything in *.gform.dart Partitioned file is treated as single file. So the imports which is not used directly in the head file are used later.

Those imports could be in reactive_forms_annotations.dart instead?

No

Additionally to avoid this kind of warning:

Ping reactive_forms author to merge this PR https://github.com/joanpablo/reactive_forms/pull/239 in order to get rid from those warnings.

stact commented 2 years ago

@vasilich6107 forked the project and tried to export those packages and it's working fine on my side

https://github.com/stact/reactive_forms_generator/blob/master/packages/reactive_forms_annotations/lib/reactive_forms_annotations.dart

Now I just need to import annotations on my model files without including unused imports

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

part 'group.freezed.dart';
part 'group.gform.dart';
vasilich6107 commented 2 years ago

It’s up to you. You can also make a helper importer file in your project which will hide those imports. For now I do not plan to add those imports into reactive_forms_annotations

stact commented 2 years ago

There is a reason? Do you need a PR?

vasilich6107 commented 2 years ago

In my opinion the annotations package does not have to solve import issues. There was also some issues in the past related to the dependency on Flutter (https://github.com/artflutter/reactive_forms_generator/pull/1)

Due to the @kuhnroyal help we were able to resolve the issues in order not to update reactive_forms structure. But for now I do not have an ability to debug tests due to the reason that there is no ability to start debugging with dart test command.

So the issues with making reacitve_forms_annotations to serve like barrel import are not obvious but pretty complicated. This is also not a bug which requires fixing)

For now I will prefer to leave everything "as is". You can create a proposal or PR for this but I suppose the resolution will wait a little bit like #8 and #10 until there will be more visible pros on importing additional stuff through reactive_forms_annotatinos

stact commented 2 years ago

Duly noted thanks for support and explanation. Let's use barrel imports for workaround.

kuhnroyal commented 2 years ago

import 'package:dartz/dartz.dart' hide State; probably would have worked without changes?