Refactor/cubit to notifier migration#42
Conversation
…nagement Migrated from Cubit (Bloc) to value notifier. Removed dependencies: flutter_bloc, rx_dart, equatable, Changes are reflected on examples Changes are reflected on tests
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| }); | ||
|
|
||
| /// The focus node of the field. | ||
| final focusNode = FocusNode(); |
| import 'package:leancode_forms/leancode_forms.dart'; | ||
|
|
||
| /// A [TextFieldController] that owns a [FocusNode] for scroll-to-error flows. | ||
| class FocusableTextFieldController<E extends Object> |
There was a problem hiding this comment.
All form controls are usually focusable, I don't think having a Focusable... wrapper for each of the ...FieldController is a good approach
There was a problem hiding this comment.
Wait why is it in example and not in the lib itself?
|
|
||
| Side-by-side snippets showing the same form scenario in three flavors: | ||
|
|
||
| 1. **0.1.x (pre-migration)** — built on `flutter_bloc` + `rxdart` |
There was a problem hiding this comment.
I don't care about older versions in EXAMPLE. That's what changelog, git history and migration document is for
|
|
||
| 1. **0.1.x (pre-migration)** — built on `flutter_bloc` + `rxdart` | ||
| 2. **0.2.0 with `FieldBuilder`** — the recommended default; tiny wrapper around `ValueListenableBuilder` | ||
| 3. **0.2.0 with `ValueListenableBuilder`** directly — for cases where you want the SDK primitive (e.g. the `child:` optimization) |
There was a problem hiding this comment.
What's the use-case for having this approach? Looking at just the API I think it would be better and simpler to keep one API for the builders
| FieldBuilder<String, ValidationError>( | ||
| field: context.read<SignupController>().email, | ||
| builder: (context, state) => TextFormField( | ||
| controller: context.read<SignupController>().email.textController, |
There was a problem hiding this comment.
We already passed the field controller, why fetch it again? Please just add the field controller itself into the builder callback params.
|
|
||
| // Render: | ||
| FieldBuilder<String, ValidationError>( | ||
| field: context.read<SignupController>().email, |
There was a problem hiding this comment.
Using context.read instead of watch/select in build() method is an antipattern
| * `FieldCubit` → `FieldController` | ||
| * `TextFieldCubit` → `TextFieldController` | ||
| * `BooleanFieldCubit` → `BooleanFieldController` | ||
| * `SingleSelectFieldCubit` → `SingleSelectFieldController` | ||
| * `MultiSelectFieldCubit` → `MultiSelectFieldController` | ||
| * `FormGroupCubit` → `FormGroupController` |
There was a problem hiding this comment.
I'm not a fan of the ...Controller naming. It's too close to TextEditingController. I'd suggest maybe Form...Field like FormBooleanField or FormTextField
| flutter_test: | ||
| sdk: flutter | ||
| leancode_lint: ^15.0.0 | ||
| leancode_lint: ^15.0.0 No newline at end of file |
| /// to be able to unambiguously detect lack of errors. | ||
| /// | ||
| /// If autovalidate is true, the validator will be run after each field change. | ||
| // ignore_for_file: avoid_positional_boolean_parameters |
There was a problem hiding this comment.
This package depends on leancode_lint 15, which has this lint disabled (in version 14) – no need to ignore it, I think
| /// | ||
| /// Introducing cycles in forms is not supported and not checked against (most | ||
| /// likely will cause a stack overflow somewhere). | ||
| // ignore_for_file: avoid_positional_boolean_parameters |
| /// If autovalidate is true, the validator will be run after each field change. | ||
| // ignore_for_file: avoid_positional_boolean_parameters | ||
| class FieldController<T, E extends Object> | ||
| extends ValueNotifier<FieldState<T, E>> { |
There was a problem hiding this comment.
Do we want to extend ValueNotifier directly, instead of e.g. ValueListenable and adding our own value field? ValueNotifier exposes a public setter, so that anyone can create and set arbitrary FieldState values
| /// The current state. Alias for [value] kept for readability at call sites | ||
| /// that previously read `cubit.state`. | ||
| FieldState<T, E> get state => value; |
There was a problem hiding this comment.
I wouldn't do that tbh. The entire undertaking is extremely breaking anyway, so I don't see a value in keeping a bloc-related alias
| ...controller.deliveryList.map( | ||
| (e) => ConsumerSubform( | ||
| key: ValueKey(e.hashCode), | ||
| form: e, | ||
| onRemove: controller.removeConsumer, | ||
| ), | ||
| ), |
| child: SingleChildScrollView( | ||
| child: Column( |
There was a problem hiding this comment.
SingleChildScrollView + Column -> ListView?
| flutter_test: | ||
| sdk: flutter | ||
| leancode_lint: ^15.0.0 | ||
| leancode_lint: ^6.0.0 |
| /// Introducing cycles in forms is not supported and not checked against (most | ||
| /// likely will cause a stack overflow somewhere). | ||
| // ignore_for_file: avoid_positional_boolean_parameters | ||
| class FormGroupController extends ValueNotifier<FormGroupState> { |
There was a problem hiding this comment.
Why does entity called FormGroupController manages entities called FieldController? Naming looks a bit inconsistent to me
| ```dart | ||
| class SimpleFormController extends FormGroupController { | ||
| SimpleFormController() { | ||
| registerFields([firstName, email]); |
There was a problem hiding this comment.
Can it be an abstract getter fields in FormGroupController that needs to be implemented in subclasses? This way it'll be required and therefore one less thing that can be forgotten.
Something like this:
class SimpleFormController extends FormGroupController {
SimpleFormController();
@override
List<FieldController> get fields => [
firstName,
email,
];
...
}There was a problem hiding this comment.
Having registerFields allows dynamically adding fields as needed (for example, conditional fields). Although for that, an unregisterFields method would be needed 🤔
| addTearDown(controller.dispose); | ||
|
|
||
| controller.email.setValue('john@email.com'); | ||
| await Future<void>.delayed(const Duration(seconds: 2)); |
There was a problem hiding this comment.
It would be nice to have a way to "await validation"
| const ListEquality<FieldController<dynamic, dynamic>>() | ||
| .equals(fields, other.fields) && | ||
| const SetEquality<FormGroupController>() | ||
| .equals(subforms, other.subforms) && |
There was a problem hiding this comment.
Consider using listEquals and setEquals from Flutter
LMG-394
flutter_bloc,rx_dart, flutter_hooks,equatable`LMG-404
Migration.mdfile