From 09dd31983792afd43a9fa699ec530630d28dc382 Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Mon, 30 Oct 2023 15:48:23 +0100 Subject: [PATCH 1/6] Add glade listeners, value transform --- glade_forms/CHANGELOG.md | 10 +++ glade_forms/example/lib/example.dart | 8 +- glade_forms/lib/src/core/changes_info.dart | 2 +- glade_forms/lib/src/core/glade_input.dart | 67 +++++++++++---- glade_forms/lib/src/model/glade_model.dart | 31 ++++++- .../lib/src/widgets/glade_form_builder.dart | 21 +++-- .../lib/src/widgets/glade_form_consumer.dart | 29 +++++++ .../lib/src/widgets/glade_form_listener.dart | 55 ++++++++++++ .../src/widgets/glade_model_debug_info.dart | 2 +- glade_forms/lib/src/widgets/widgets.dart | 2 + glade_forms/pubspec.yaml | 2 +- glade_forms/test/glade_input_test.dart | 2 +- glade_forms/test/model/group_edit_test.dart | 85 +++++++++++++++++++ .../complex_object_mapping_example.dart | 4 +- .../one_checkbox_deps_validation.dart | 2 +- .../lib/usecases/quickstart_example.dart | 8 +- .../lib/usecases/two_way_checkbox_change.dart | 6 +- 17 files changed, 294 insertions(+), 42 deletions(-) create mode 100644 glade_forms/lib/src/widgets/glade_form_consumer.dart create mode 100644 glade_forms/lib/src/widgets/glade_form_listener.dart create mode 100644 glade_forms/test/model/group_edit_test.dart diff --git a/glade_forms/CHANGELOG.md b/glade_forms/CHANGELOG.md index 4c04ea8..d70d7f4 100644 --- a/glade_forms/CHANGELOG.md +++ b/glade_forms/CHANGELOG.md @@ -1,3 +1,13 @@ +## 1.2.0 +- **[Feat]**: Add `GladeFormListener` widget allowing to listen for model's changes +- **[Feat]**: Add `groupEdit()` method in GladeModel allows to update multiple inputs at once. + - Works great with `GladeFormListener` +- **[Feat]**: Add `valueTransform` in GladeInput. Transform value before it is assigned into value. + - Firstly `stringToTypeConverter` is called if needed, then `valueTransform`. +- **[Feat]**: Add `updateValue(T value)` as shorthand for inputs when field is not TextField. +- **[Breaking]**: `inputKey` is now **required**. + - This change will prevent for listener's errors. + ## 1.1.2 - Fix links in readme diff --git a/glade_forms/example/lib/example.dart b/glade_forms/example/lib/example.dart index ff22f53..818d7a3 100644 --- a/glade_forms/example/lib/example.dart +++ b/glade_forms/example/lib/example.dart @@ -12,9 +12,9 @@ class _Model extends GladeModel { @override void initialize() { - name = GladeInput.stringInput(); - age = GladeInput.intInput(value: 0); - email = GladeInput.stringInput(validator: (validator) => (validator..isEmail()).build()); + name = GladeInput.stringInput(inputKey: 'name'); + age = GladeInput.intInput(value: 0, inputKey: 'age'); + email = GladeInput.stringInput(validator: (validator) => (validator..isEmail()).build(), inputKey: 'email'); super.initialize(); } @@ -25,7 +25,7 @@ class Example extends StatelessWidget { @override Widget build(BuildContext context) { - return GladeFormBuilder( + return GladeFormBuilder.create( create: (context) => _Model(), builder: (context, model, _) => Padding( padding: const EdgeInsets.all(32), diff --git a/glade_forms/lib/src/core/changes_info.dart b/glade_forms/lib/src/core/changes_info.dart index 6959932..1bb8067 100644 --- a/glade_forms/lib/src/core/changes_info.dart +++ b/glade_forms/lib/src/core/changes_info.dart @@ -3,7 +3,7 @@ import 'package:glade_forms/src/validator/validator_result.dart'; class ChangesInfo extends Equatable { final T? initialValue; - final T previousValue; + final T? previousValue; final T value; final ValidatorResult? validatorResult; diff --git a/glade_forms/lib/src/core/glade_input.dart b/glade_forms/lib/src/core/glade_input.dart index 95949c9..e851f23 100644 --- a/glade_forms/lib/src/core/glade_input.dart +++ b/glade_forms/lib/src/core/glade_input.dart @@ -13,11 +13,13 @@ import 'package:glade_forms/src/validator/validator_result.dart'; typedef ValueComparator = bool Function(T? initial, T? value); typedef ValidatorFactory = ValidatorInstance Function(GladeValidator v); typedef StringValidatorFactory = ValidatorInstance Function(StringValidator validator); - typedef OnChange = void Function(ChangesInfo info, InputDependencies dependencies); +typedef ValueTransform = T Function(T input); typedef StringInput = GladeInput; +T _defaultTransform(T input) => input; + class GladeInput extends ChangeNotifier { /// Compares initial and current value. @protected @@ -32,7 +34,7 @@ class GladeInput extends ChangeNotifier { final InputDependenciesFactory dependenciesFactory; /// An input's identification. - final String? inputKey; + final String inputKey; /// Initial value - does not change after creating. final T? initialValue; @@ -45,6 +47,9 @@ class GladeInput extends ChangeNotifier { /// Called when input's value changed. OnChange? onChange; + /// Transforms passed value before assigning it into input. + ValueTransform valueTransform; + TextEditingController? _textEditingController; final StringToTypeConverter _defaultConverter = StringToTypeConverter(converter: (x, _) => x as T); @@ -52,6 +57,9 @@ class GladeInput extends ChangeNotifier { /// Current input's value. T _value; + /// Previous inputs'value. + T? _previousValue; + /// Input did not updated its value from initialValue. bool _isPure; @@ -64,6 +72,8 @@ class GladeInput extends ChangeNotifier { T get value => _value; + T? get previousValue => _previousValue; + /// Input's value was not changed. bool get isPure => _isPure; @@ -84,15 +94,15 @@ class GladeInput extends ChangeNotifier { /// String representattion of [value]. String get stringValue => stringTovalueConverter?.convertBack(value) ?? value.toString(); - // ignore: no_runtimetype_tostring, in this case it is ok - only for dev purposes - String get inputName => inputKey ?? '$runtimeType($value)'; + /// Equals to [inputKey]. + //String get inputName => inputKey; set value(T value) { - final previousValue = _value; - _value = value; + _previousValue = _value; - final strValue = stringValue; + _value = valueTransform(value); + final strValue = stringValue; // synchronize text controller with value _textEditingController?.value = TextEditingValue( text: strValue, @@ -105,7 +115,7 @@ class GladeInput extends ChangeNotifier { // propagate input's changes onChange?.call( ChangesInfo( - previousValue: previousValue, + previousValue: _previousValue, value: value, initialValue: initialValue, validatorResult: validate(), @@ -113,7 +123,7 @@ class GladeInput extends ChangeNotifier { dependenciesFactory(), ); - _bindedModel?.notifyInputUpdated(); + _bindedModel?.notifyInputUpdated(this); notifyListeners(); } @@ -130,6 +140,7 @@ class GladeInput extends ChangeNotifier { required this.dependenciesFactory, required this.defaultTranslations, required this.onChange, + required this.valueTransform, TextEditingController? textEditingController, bool createTextController = true, }) : _isPure = isPure, @@ -149,8 +160,8 @@ class GladeInput extends ChangeNotifier { GladeInput.pure( T value, { + required String inputKey, T? initialValue, - String? inputKey, ValueComparator? valueComparator, StringToTypeConverter? valueConverter, ValidatorInstance? validatorInstance, @@ -160,6 +171,7 @@ class GladeInput extends ChangeNotifier { OnChange? onChange, TextEditingController? textEditingController, bool createTextController = true, + ValueTransform? valueTransform, }) : this( value: value, isPure: true, @@ -174,12 +186,13 @@ class GladeInput extends ChangeNotifier { onChange: onChange, textEditingController: textEditingController, createTextController: createTextController, + valueTransform: valueTransform ?? _defaultTransform, ); GladeInput.dirty( T value, { + required String inputKey, T? initialValue, - String? inputKey, ValueComparator? valueComparator, StringToTypeConverter? valueConverter, ValidatorInstance? validatorInstance, @@ -189,6 +202,7 @@ class GladeInput extends ChangeNotifier { OnChange? onChange, TextEditingController? textEditingController, bool createTextController = true, + ValueTransform? valueTransform, }) : this( value: value, isPure: false, @@ -203,11 +217,13 @@ class GladeInput extends ChangeNotifier { onChange: onChange, textEditingController: textEditingController, createTextController: createTextController, + valueTransform: valueTransform ?? _defaultTransform, ); factory GladeInput.create({ /// Sets current value of input. required T value, + required String inputKey, ValidatorFactory? validator, /// Initial value when GenericInput is created. @@ -217,12 +233,12 @@ class GladeInput extends ChangeNotifier { bool pure = true, ErrorTranslator? translateError, ValueComparator? comparator, - String? inputKey, StringToTypeConverter? valueConverter, InputDependenciesFactory? dependencies, OnChange? onChange, TextEditingController? textEditingController, bool createTextController = true, + ValueTransform? valueTransform, }) { final validatorInstance = validator?.call(GladeValidator()) ?? GladeValidator().build(); @@ -239,6 +255,7 @@ class GladeInput extends ChangeNotifier { onChange: onChange, textEditingController: textEditingController, createTextController: createTextController, + valueTransform: valueTransform, ) : GladeInput.dirty( value, @@ -252,6 +269,7 @@ class GladeInput extends ChangeNotifier { onChange: onChange, textEditingController: textEditingController, createTextController: createTextController, + valueTransform: valueTransform, ); } @@ -262,16 +280,17 @@ class GladeInput extends ChangeNotifier { /// In case of need of any validation use [GladeInput.create] directly. factory GladeInput.optional({ required T value, + required String inputKey, T? initialValue, bool pure = true, ErrorTranslator? translateError, ValueComparator? comparator, - String? inputKey, StringToTypeConverter? valueConverter, InputDependenciesFactory? dependencies, OnChange? onChange, TextEditingController? textEditingController, bool createTextController = true, + ValueTransform? valueTransform, }) => GladeInput.create( validator: (v) => v.build(), @@ -286,6 +305,7 @@ class GladeInput extends ChangeNotifier { onChange: onChange, textEditingController: textEditingController, createTextController: createTextController, + valueTransform: valueTransform, ); /// Predefined GenericInput with predefined `notNull` validation. @@ -293,16 +313,17 @@ class GladeInput extends ChangeNotifier { /// In case of need of any aditional validation use [GladeInput.create] directly. factory GladeInput.required({ required T value, + required String inputKey, T? initialValue, bool pure = true, ErrorTranslator? translateError, ValueComparator? comparator, - String? inputKey, StringToTypeConverter? valueConverter, InputDependenciesFactory? dependencies, OnChange? onChange, TextEditingController? textEditingController, bool createTextController = true, + ValueTransform? valueTransform, }) => GladeInput.create( validator: (v) => (v..notNull()).build(), @@ -317,6 +338,7 @@ class GladeInput extends ChangeNotifier { onChange: onChange, textEditingController: textEditingController, createTextController: createTextController, + valueTransform: valueTransform, ); // ignore: use_setters_to_change_properties, as method. @@ -324,16 +346,17 @@ class GladeInput extends ChangeNotifier { static GladeInput intInput({ required int value, + required String inputKey, ValidatorFactory? validator, int? initialValue, bool pure = true, ErrorTranslator? translateError, ValueComparator? comparator, - String? inputKey, InputDependenciesFactory? dependencies, OnChange? onChange, TextEditingController? textEditingController, bool createTextController = true, + ValueTransform? valueTransform, }) => GladeInput.create( value: value, @@ -348,20 +371,22 @@ class GladeInput extends ChangeNotifier { onChange: onChange, textEditingController: textEditingController, createTextController: createTextController, + valueTransform: valueTransform, ); static GladeInput boolInput({ required bool value, + required String inputKey, ValidatorFactory? validator, bool? initialValue, bool pure = true, ErrorTranslator? translateError, ValueComparator? comparator, - String? inputKey, InputDependenciesFactory? dependencies, OnChange? onChange, TextEditingController? textEditingController, bool createTextController = true, + ValueTransform? valueTransform, }) => GladeInput.create( value: value, @@ -376,16 +401,17 @@ class GladeInput extends ChangeNotifier { onChange: onChange, textEditingController: textEditingController, createTextController: createTextController, + valueTransform: valueTransform, ); static GladeInput stringInput({ + required String inputKey, String? value, StringValidatorFactory? validator, String? initialValue, bool pure = true, ErrorTranslator? translateError, DefaultTranslations? defaultTranslations, - String? inputKey, InputDependenciesFactory? dependencies, OnChange? onChange, TextEditingController? textEditingController, @@ -485,13 +511,16 @@ class GladeInput extends ChangeNotifier { } } + // ignore: use_setters_to_change_properties, used as shorthand for field setter. + void updateValue(T value) => this.value = value; + @protected GladeInput copyWith({ + String? inputKey, ValueComparator? valueComparator, ValidatorInstance? validatorInstance, StringToTypeConverter? stringTovalueConverter, InputDependenciesFactory? dependenciesFactory, - String? inputKey, T? initialValue, ErrorTranslator? translateError, T? value, @@ -501,6 +530,7 @@ class GladeInput extends ChangeNotifier { TextEditingController? textEditingController, // ignore: avoid-unused-parameters, it is here just to be linter happy ¯\_(ツ)_/¯ bool? createTextController, + ValueTransform? valueTransform, }) { return GladeInput( value: value ?? this.value, @@ -515,6 +545,7 @@ class GladeInput extends ChangeNotifier { defaultTranslations: defaultTranslations ?? this.defaultTranslations, onChange: onChange ?? this.onChange, textEditingController: textEditingController ?? this._textEditingController, + valueTransform: valueTransform ?? this.valueTransform, ); } diff --git a/glade_forms/lib/src/model/glade_model.dart b/glade_forms/lib/src/model/glade_model.dart index 822623f..cd60f56 100644 --- a/glade_forms/lib/src/model/glade_model.dart +++ b/glade_forms/lib/src/model/glade_model.dart @@ -3,6 +3,9 @@ import 'package:glade_forms/src/core/core.dart'; import 'package:meta/meta.dart'; abstract class GladeModel extends ChangeNotifier { + List> _lastUpdates = []; + bool _groupEdit = false; + bool get isValid => inputs.every((input) => input.isValid); bool get isNotValid => !isValid; @@ -15,6 +18,9 @@ abstract class GladeModel extends ChangeNotifier { List> get inputs; + List get lastUpdatedInputKeys => + _lastUpdates.map((e) => e.inputKey).where((element) => element != null).cast().toList(); + /// Formats errors from `inputs`. String get formattedValidationErrors => inputs.map((e) { if (e.hasConversionError) return '${e.inputKey ?? e.runtimeType} - CONVERSION ERROR'; @@ -56,10 +62,33 @@ abstract class GladeModel extends ChangeNotifier { void updateInput, T>(INPUT input, T value) { if (input.value == value) return; + _lastUpdates = [input]; + input.value = value; notifyListeners(); } @internal - void notifyInputUpdated() => notifyListeners(); + void notifyInputUpdated(GladeInput input) { + if (_groupEdit) { + _lastUpdates.add(input); + } else { + _lastUpdates = [input]; + } + + if (!_groupEdit) { + notifyListeners(); + } + } + + /// Use it to update multiple inputs at once before these changes are popragated through notifyListeners(). + void groupEdit(void Function() edit) { + _groupEdit = true; + + edit(); + + _groupEdit = false; + + notifyListeners(); + } } diff --git a/glade_forms/lib/src/widgets/glade_form_builder.dart b/glade_forms/lib/src/widgets/glade_form_builder.dart index c65b4b2..105473f 100644 --- a/glade_forms/lib/src/widgets/glade_form_builder.dart +++ b/glade_forms/lib/src/widgets/glade_form_builder.dart @@ -12,17 +12,11 @@ class GladeFormBuilder extends StatelessWidget { final Widget? child; factory GladeFormBuilder({ - required CreateModelFunction create, required GladeFormWidgetBuilder builder, Key? key, Widget? child, }) => - GladeFormBuilder._( - builder: builder, - create: create, - key: key, - child: child, - ); + GladeFormBuilder._(builder: builder, key: key, child: child); const GladeFormBuilder._({ required this.builder, @@ -32,6 +26,19 @@ class GladeFormBuilder extends StatelessWidget { this.child, }); + factory GladeFormBuilder.create({ + required CreateModelFunction create, + required GladeFormWidgetBuilder builder, + Widget? child, + Key? key, + }) => + GladeFormBuilder._( + builder: builder, + create: create, + key: key, + child: child, + ); + factory GladeFormBuilder.value({ required GladeFormWidgetBuilder builder, required M value, diff --git a/glade_forms/lib/src/widgets/glade_form_consumer.dart b/glade_forms/lib/src/widgets/glade_form_consumer.dart new file mode 100644 index 0000000..ab2ea6b --- /dev/null +++ b/glade_forms/lib/src/widgets/glade_form_consumer.dart @@ -0,0 +1,29 @@ +import 'package:flutter/widgets.dart'; +import 'package:glade_forms/src/model/glade_model.dart'; +import 'package:glade_forms/src/widgets/glade_form_builder.dart'; +import 'package:glade_forms/src/widgets/glade_form_listener.dart'; + +class GladeFormConsumer extends StatelessWidget { + final GladeFormWidgetBuilder builder; + final GladeFormListenerFn? listener; + final Widget? child; + + const GladeFormConsumer({ + required this.builder, + super.key, + this.listener, + this.child, + }); + + @override + Widget build(BuildContext context) { + if (listener case final listenerValue?) { + return GladeFormListener( + listener: listenerValue, + child: GladeFormBuilder(builder: builder, child: child), + ); + } + + return GladeFormBuilder(builder: builder, child: child); + } +} diff --git a/glade_forms/lib/src/widgets/glade_form_listener.dart b/glade_forms/lib/src/widgets/glade_form_listener.dart new file mode 100644 index 0000000..dc37942 --- /dev/null +++ b/glade_forms/lib/src/widgets/glade_form_listener.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:glade_forms/src/model/glade_model.dart'; +import 'package:provider/provider.dart'; + +typedef GladeFormListenerFn = void Function( + BuildContext context, + M model, + List lastUpdatedInputKey, +); + +class GladeFormListener extends StatefulWidget { + final Widget child; + final GladeFormListenerFn listener; + + const GladeFormListener({ + required this.listener, + required this.child, + super.key, + }); + + @override + State> createState() => _GladeFormListenerState(); +} + +class _GladeFormListenerState extends State> { + M? model; + + @override + void initState() { + super.initState(); + context.read().addListener(_onModelUpdate); + } + + @override + void dispose() { + model?.removeListener(_onModelUpdate); + super.dispose(); + } + + @override + void didChangeDependencies() { + model = context.read(); + super.didChangeDependencies(); + } + + void _onModelUpdate() { + final m = context.read(); + widget.listener(context, m, m.lastUpdatedInputKeys); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} diff --git a/glade_forms/lib/src/widgets/glade_model_debug_info.dart b/glade_forms/lib/src/widgets/glade_model_debug_info.dart index 748bf0a..01b2517 100644 --- a/glade_forms/lib/src/widgets/glade_model_debug_info.dart +++ b/glade_forms/lib/src/widgets/glade_model_debug_info.dart @@ -70,7 +70,7 @@ class GladeModelDebugInfo extends StatelessWidget { for (final x in model.inputs) TableRow( children: [ - Center(child: Text(x.inputName)), + Center(child: Text(x.inputKey)), Center(child: Text(x.isUnchanged.toString())), Center(child: Text(x.isValid.toString())), Center(child: Text(x.errorFormatted())), diff --git a/glade_forms/lib/src/widgets/widgets.dart b/glade_forms/lib/src/widgets/widgets.dart index 05cc8bd..020828a 100644 --- a/glade_forms/lib/src/widgets/widgets.dart +++ b/glade_forms/lib/src/widgets/widgets.dart @@ -1,3 +1,5 @@ export 'glade_form_builder.dart'; +export 'glade_form_consumer.dart'; +export 'glade_form_listener.dart'; export 'glade_model_debug_info.dart'; export 'glade_model_provider.dart'; diff --git a/glade_forms/pubspec.yaml b/glade_forms/pubspec.yaml index af5eda7..f1719f7 100644 --- a/glade_forms/pubspec.yaml +++ b/glade_forms/pubspec.yaml @@ -1,6 +1,6 @@ name: glade_forms description: A universal way to define form validators with support of translations. -version: 1.1.2 +version: 1.2.0 repository: https://github.com/netglade/glade_forms issue_tracker: https://github.com/netglade/glade_forms/issues screenshots: diff --git a/glade_forms/test/glade_input_test.dart b/glade_forms/test/glade_input_test.dart index e3c611c..81b7c28 100644 --- a/glade_forms/test/glade_input_test.dart +++ b/glade_forms/test/glade_input_test.dart @@ -3,7 +3,7 @@ import 'package:test/test.dart'; void main() { test('GladeInput with non-nullable type', () { - final input = GladeInput.create(validator: (v) => v.build(), value: 0); + final input = GladeInput.create(validator: (v) => v.build(), value: 0, inputKey: 'a'); expect(input.isValid, isTrue); }); diff --git a/glade_forms/test/model/group_edit_test.dart b/glade_forms/test/model/group_edit_test.dart new file mode 100644 index 0000000..dd580d3 --- /dev/null +++ b/glade_forms/test/model/group_edit_test.dart @@ -0,0 +1,85 @@ +import 'package:glade_forms/glade_forms.dart'; +import 'package:test/test.dart'; + +class _Model extends GladeModel { + late GladeInput a; + late GladeInput b; + + @override + List> get inputs => [a, b]; + + @override + void initialize() { + a = GladeInput.intInput(value: 0, inputKey: 'a'); + b = GladeInput.intInput(value: 1, inputKey: 'b'); + + super.initialize(); + } +} + +void main() { + test('When updating [a] listeners is called', () { + final model = _Model(); + var listenerCount = 0; + model.addListener(() { + listenerCount++; + }); + + model.a.value = 5; + expect(model.a.value, equals(5)); + expect(model.lastUpdatedInputKeys, equals(['a'])); + expect(listenerCount, equals(1), reason: 'Should be called'); + }); + + test('When updating [a] listeners is called', () { + final model = _Model(); + var listenerCount = 0; + model.addListener(() { + listenerCount++; + }); + + model.b.value = 5; + expect(model.b.value, equals(5)); + expect(model.lastUpdatedInputKeys, equals(['b'])); + expect(listenerCount, equals(1), reason: 'Should be called'); + }); + + test('When updating [a] and [b] listeners are called two times', () { + final model = _Model(); + var listenerCount = 0; + model.addListener(() { + listenerCount++; + }); + + model.a.value = 3; + + expect(model.a.value, equals(3)); + expect(model.lastUpdatedInputKeys, equals(['a'])); + + model.b.value = 5; + + expect(model.b.value, equals(5)); + expect(model.lastUpdatedInputKeys, equals(['b'])); + expect(listenerCount, equals(2), reason: 'Should be called'); + }); + + test('When updating [a] and [b] at once listener is called once', () { + final model = _Model(); + var listenerCount = 0; + model.addListener(() { + listenerCount++; + }); + + // ignore: cascade_invocations, under test ok. + model.groupEdit(() { + model.a.value = 3; + model.b.value = 5; + }); + + expect(model.a.value, equals(3)); + expect(model.b.value, equals(5)); + + expect(model.lastUpdatedInputKeys, equals(['a', 'b'])); + expect(listenerCount, equals(1), reason: 'Should be called'); + }); +} diff --git a/storybook/lib/usecases/complex_object_mapping_example.dart b/storybook/lib/usecases/complex_object_mapping_example.dart index 290eb09..d10c346 100644 --- a/storybook/lib/usecases/complex_object_mapping_example.dart +++ b/storybook/lib/usecases/complex_object_mapping_example.dart @@ -28,10 +28,12 @@ class _Model extends GladeModel { selectedItem = GladeInput.create( validator: (v) => (v..notNull()).build(), value: null, + inputKey: 'selectedItem', ); availableStats = GladeInput.create( validator: (v) => v.build(), value: [], + inputKey: 'stats', valueConverter: StringToTypeConverter( converter: (rawInput, cantConvert) { final r = RegExp(r'^\d+(,\s*\d+\s*)*$'); @@ -65,7 +67,7 @@ class ComplexObjectMappingExample extends StatelessWidget { return UsecaseContainer( shortDescription: 'Converters and complex objects', className: 'complex_object_mapping_example.dart', - child: GladeFormBuilder( + child: GladeFormBuilder.create( create: (context) => _Model(), builder: (context, model, _) { return Form( diff --git a/storybook/lib/usecases/one_checkbox_deps_validation.dart b/storybook/lib/usecases/one_checkbox_deps_validation.dart index 1a414e0..c0e5783 100644 --- a/storybook/lib/usecases/one_checkbox_deps_validation.dart +++ b/storybook/lib/usecases/one_checkbox_deps_validation.dart @@ -78,7 +78,7 @@ If *VIP content* is checked, **age** must be over 18. shortDescription: "Age input validation depends on checkbox's value", description: markdownData, className: 'one_checkbox_deps_validation.dart', - child: GladeFormBuilder( + child: GladeFormBuilder.create( create: (context) => AgeRestrictedModel(), builder: (context, formModel, _) => Padding( padding: const EdgeInsets.all(8), diff --git a/storybook/lib/usecases/quickstart_example.dart b/storybook/lib/usecases/quickstart_example.dart index 68e2adc..7b262e8 100644 --- a/storybook/lib/usecases/quickstart_example.dart +++ b/storybook/lib/usecases/quickstart_example.dart @@ -14,9 +14,9 @@ class _Model extends GladeModel { @override void initialize() { - name = GladeInput.stringInput(); - age = GladeInput.intInput(value: 0); - email = GladeInput.stringInput(validator: (validator) => (validator..isEmail()).build()); + name = GladeInput.stringInput(inputKey: 'name'); + age = GladeInput.intInput(value: 0, inputKey: 'age'); + email = GladeInput.stringInput(validator: (validator) => (validator..isEmail()).build(), inputKey: 'email'); super.initialize(); } @@ -29,7 +29,7 @@ class QuickStartExample extends StatelessWidget { Widget build(BuildContext context) { return UsecaseContainer( shortDescription: 'Quick start example', - child: GladeFormBuilder( + child: GladeFormBuilder.create( create: (context) => _Model(), builder: (context, model, _) => Padding( padding: const EdgeInsets.all(32), diff --git a/storybook/lib/usecases/two_way_checkbox_change.dart b/storybook/lib/usecases/two_way_checkbox_change.dart index 02aac4e..396453c 100644 --- a/storybook/lib/usecases/two_way_checkbox_change.dart +++ b/storybook/lib/usecases/two_way_checkbox_change.dart @@ -67,7 +67,9 @@ class AgeRestrictedModel extends GladeModel { final age = dependencies.byKey('age-input'); if (info.value && age.value < 18) { - age.value = 18; + groupEdit(() { + age.value = 18; + }); } }, ); @@ -91,7 +93,7 @@ If *age* is changed to value under 18, *vip content* is unchecked and vice-versa shortDescription: "Age input depends on checkbox's value automatically", description: markdownData, className: 'two_way_checkbox_change.dart', - child: GladeFormBuilder( + child: GladeFormBuilder.create( create: (context) => AgeRestrictedModel(), builder: (context, formModel, _) => Padding( padding: const EdgeInsets.all(8), From 58aaced294c3230e4e8d5d565c31d8f4938270f1 Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Tue, 31 Oct 2023 17:02:51 +0100 Subject: [PATCH 2/6] Fix isUnchanged computation --- glade_forms/lib/src/core/glade_input.dart | 2 +- glade_forms/lib/src/model/glade_model.dart | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/glade_forms/lib/src/core/glade_input.dart b/glade_forms/lib/src/core/glade_input.dart index e851f23..3f24388 100644 --- a/glade_forms/lib/src/core/glade_input.dart +++ b/glade_forms/lib/src/core/glade_input.dart @@ -82,7 +82,7 @@ class GladeInput extends ChangeNotifier { /// [value] is equal to [initialValue]. /// /// Can be dirty or pure. - bool get isUnchanged => (valueComparator?.call(initialValue, value) ?? value) == initialValue; + bool get isUnchanged => valueComparator?.call(initialValue, value) ?? (value == initialValue); /// Input does not have conversion error nor validation error. bool get isValid => !_conversionError && _validator(value).isValid; diff --git a/glade_forms/lib/src/model/glade_model.dart b/glade_forms/lib/src/model/glade_model.dart index cd60f56..59a9695 100644 --- a/glade_forms/lib/src/model/glade_model.dart +++ b/glade_forms/lib/src/model/glade_model.dart @@ -18,18 +18,17 @@ abstract class GladeModel extends ChangeNotifier { List> get inputs; - List get lastUpdatedInputKeys => - _lastUpdates.map((e) => e.inputKey).where((element) => element != null).cast().toList(); + List get lastUpdatedInputKeys => _lastUpdates.map((e) => e.inputKey).toList(); /// Formats errors from `inputs`. String get formattedValidationErrors => inputs.map((e) { - if (e.hasConversionError) return '${e.inputKey ?? e.runtimeType} - CONVERSION ERROR'; + if (e.hasConversionError) return '${e.inputKey} - CONVERSION ERROR'; if (e.validatorResult.isInvalid) { - return '${e.inputKey ?? e.runtimeType} - ${e.errorFormatted()}'; + return '${e.inputKey} - ${e.errorFormatted()}'; } - return '${e.inputKey ?? e.runtimeType} - VALID'; + return '${e.inputKey} - VALID'; }).join('\n'); List get errors => inputs.map((e) => e.validatorResult).toList(); From 92ac5bffe7463fe2ede72f83b0645b48a32ec687 Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Tue, 31 Oct 2023 21:03:15 +0100 Subject: [PATCH 3/6] Update readme --- glade_forms/README.md | 43 +++++++++++++++++++-- glade_forms/doc/flow-input-chart.png | Bin 0 -> 59860 bytes glade_forms/lib/src/core/glade_input.dart | 4 ++ glade_forms/lib/src/model/glade_model.dart | 5 +++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 glade_forms/doc/flow-input-chart.png diff --git a/glade_forms/README.md b/glade_forms/README.md index 42910ef..d45492d 100644 --- a/glade_forms/README.md +++ b/glade_forms/README.md @@ -22,7 +22,8 @@ A universal way to define form validators with support of translations. - [Validation](#validation) - [Using validators without GladeInput](#using-validators-without-gladeinput) - [GladeModel](#glademodel) - - [`GladeFormBuilder` and `GladeFormProvider`](#gladeformbuilder-and-gladeformprovider) + - [Flutter widgets](#flutter-widgets) + - [Edit multiple inputs at once](#edit-multiple-inputs-at-once) - [Dependencies](#dependencies) - [Controlling other inputs](#controlling-other-inputs) - [Translation](#translation) @@ -125,13 +126,18 @@ On each input we can define - **translateError** - If there are validation errors, function for error translations can be provided. - **inputKey** - For debug purposes and dependencies, each input can have unique name for simple identification. - **dependencies** - Each input can depend on another inputs for validation. - - **valueConverter** - If input is used by TextField and `T` is not a `String`, value converter should be provided. + - **stringTovalueConverter** - If input is used by TextField and `T` is not a `String`, value converter should be provided. - **valueComparator** - Sometimes it is handy to provied `initialValue` which will be never updated after input is mutated. `valueComparator` should be provided to compare `initialValue` and `value` if `T` is not comparable type by default. + - **valueTransform** - transform `T` value into different `T` value. An example of usage can be sanitazation of string input (trim(),...). - **defaultTranslation** - If error's translations are simple, the default translation settings can be set instead of custom `translateError` method. - **textEditingController** - It is possible to provide custom instance of controller instead of default one. Most of the time, input is created with `.create()` factory with defined validation, translation and other properties. +An overview how each input's value is updated. If needed it is converted from `string` into `T`, then transformed via `valueTransform` (if provided), after that new value is set. + +![input-flow-example](https://raw.githubusercontent.com/netglade/glade_forms/main/glade_forms/doc/flow-input-chart.png) + #### StringInput StringInput is specialized variant of GladeInput which has additional, string related, validations such as `isEmail`, `isUrl`, `maxLength` and more. @@ -198,10 +204,39 @@ and properties such as `isValid` or `formattedErrors` will not work. For updating input call either `updateValueWithString(String?)` to update `T` value with string (will be converted if needed) or set `value` directly (via setter). -#### `GladeFormBuilder` and `GladeFormProvider` +#### Flutter widgets `GladeModelProvider` is predefined widget to provide `GladeModel` to widget's subtreee. -Similarly `GladeFormBuilder` allows to listen to model's changes and rebuilts its child. + +`GladeFormBuilder` allows to listen to model's changes and rebuilts its child. + +`GladeFormListener` allows to listen to model's changes and react to it. Useful for invoking side-effects such as showing dialogs, snackbars etc. `listener` provides `lastUpdatedKeys` which is list of last updated input keys. + +`GladeFormConsumer` combines GladeFormBuilder and GladeFormListener together. + +#### Edit multiple inputs at once +With each update of input, via update or setting `.value` directly, listeners (if any) are triggered. Sometimes it is needed to edit multiple inputs at once and triggering listener in the end. + +For editing multiple values use `groupEdit()`. It takes void callback to update inputs. + +An example + +```dart +class FormModel extends GladeModel { + late GladeInput age; + late GladeInput name; + + // .... + + groupEdit(() { + age.value = 18; + name.value = 'default john', + }); +} + +``` + +After that listener will contain `lastUpdatedKeys` with keys of `age` and `name` inputs. ### Dependencies Input can have dependencies on other inputs to allow dependent validation. Define input's dependencies with `dependencies`. diff --git a/glade_forms/doc/flow-input-chart.png b/glade_forms/doc/flow-input-chart.png new file mode 100644 index 0000000000000000000000000000000000000000..e6c0c03e3f75b9b76570671c833696c147d57234 GIT binary patch literal 59860 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 literal 0 HcmV?d00001 diff --git a/glade_forms/lib/src/core/glade_input.dart b/glade_forms/lib/src/core/glade_input.dart index 3f24388..01c8288 100644 --- a/glade_forms/lib/src/core/glade_input.dart +++ b/glade_forms/lib/src/core/glade_input.dart @@ -9,6 +9,7 @@ import 'package:glade_forms/src/core/type_helper.dart'; import 'package:glade_forms/src/model/glade_model.dart'; import 'package:glade_forms/src/validator/validator.dart'; import 'package:glade_forms/src/validator/validator_result.dart'; +import 'package:meta/meta.dart'; typedef ValueComparator = bool Function(T? initial, T? value); typedef ValidatorFactory = ValidatorInstance Function(GladeValidator v); @@ -34,6 +35,8 @@ class GladeInput extends ChangeNotifier { final InputDependenciesFactory dependenciesFactory; /// An input's identification. + /// + /// Used within listener changes and dependency related funcions such as validation. final String inputKey; /// Initial value - does not change after creating. @@ -341,6 +344,7 @@ class GladeInput extends ChangeNotifier { valueTransform: valueTransform, ); + @internal // ignore: use_setters_to_change_properties, as method. void bindToModel(GladeModel model) => _bindedModel = model; diff --git a/glade_forms/lib/src/model/glade_model.dart b/glade_forms/lib/src/model/glade_model.dart index 59a9695..2010a35 100644 --- a/glade_forms/lib/src/model/glade_model.dart +++ b/glade_forms/lib/src/model/glade_model.dart @@ -44,6 +44,11 @@ abstract class GladeModel extends ChangeNotifier { @mustBeOverridden @protected void initialize() { + assert( + inputs.map((e) => e.inputKey).length == inputs.map((e) => e.inputKey).toSet().length, + 'Model contains inputs with duplicated key!', + ); + for (final input in inputs) { input.bindToModel(this); } From 0311235cf6b3f99c219bedd2f24366f0a0a0cb06 Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Tue, 31 Oct 2023 21:04:22 +0100 Subject: [PATCH 4/6] Code cleanup --- glade_forms/lib/src/core/glade_input.dart | 3 --- glade_forms/lib/src/model/glade_model.dart | 3 --- 2 files changed, 6 deletions(-) diff --git a/glade_forms/lib/src/core/glade_input.dart b/glade_forms/lib/src/core/glade_input.dart index 01c8288..4a45060 100644 --- a/glade_forms/lib/src/core/glade_input.dart +++ b/glade_forms/lib/src/core/glade_input.dart @@ -97,9 +97,6 @@ class GladeInput extends ChangeNotifier { /// String representattion of [value]. String get stringValue => stringTovalueConverter?.convertBack(value) ?? value.toString(); - /// Equals to [inputKey]. - //String get inputName => inputKey; - set value(T value) { _previousValue = _value; diff --git a/glade_forms/lib/src/model/glade_model.dart b/glade_forms/lib/src/model/glade_model.dart index 2010a35..b81538f 100644 --- a/glade_forms/lib/src/model/glade_model.dart +++ b/glade_forms/lib/src/model/glade_model.dart @@ -78,9 +78,6 @@ abstract class GladeModel extends ChangeNotifier { _lastUpdates.add(input); } else { _lastUpdates = [input]; - } - - if (!_groupEdit) { notifyListeners(); } } From 578f61303219a599f21a63a71d201a86718f228c Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Wed, 1 Nov 2023 11:32:56 +0100 Subject: [PATCH 5/6] Fix conversion error, add reset to pure. Code cleanup --- glade_forms/CHANGELOG.md | 4 +- glade_forms/lib/src/core/glade_input.dart | 261 +++++++++------------- 2 files changed, 112 insertions(+), 153 deletions(-) diff --git a/glade_forms/CHANGELOG.md b/glade_forms/CHANGELOG.md index d70d7f4..98a7028 100644 --- a/glade_forms/CHANGELOG.md +++ b/glade_forms/CHANGELOG.md @@ -5,8 +5,8 @@ - **[Feat]**: Add `valueTransform` in GladeInput. Transform value before it is assigned into value. - Firstly `stringToTypeConverter` is called if needed, then `valueTransform`. - **[Feat]**: Add `updateValue(T value)` as shorthand for inputs when field is not TextField. -- **[Breaking]**: `inputKey` is now **required**. - - This change will prevent for listener's errors. +- **[Feat]**: Add `resetToPure` method allowing to reset input into pure state. +- **[Fix]**: Conversion error does not update model's stats and formatted errors. ## 1.1.2 - Fix links in readme diff --git a/glade_forms/lib/src/core/glade_input.dart b/glade_forms/lib/src/core/glade_input.dart index 4a45060..51c9b1d 100644 --- a/glade_forms/lib/src/core/glade_input.dart +++ b/glade_forms/lib/src/core/glade_input.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/widgets.dart'; import 'package:glade_forms/src/converters/glade_type_converters.dart'; import 'package:glade_forms/src/core/changes_info.dart'; @@ -39,9 +41,6 @@ class GladeInput extends ChangeNotifier { /// Used within listener changes and dependency related funcions such as validation. final String inputKey; - /// Initial value - does not change after creating. - final T? initialValue; - final ErrorTranslator? translateError; /// Validation message for conversion error. @@ -53,6 +52,9 @@ class GladeInput extends ChangeNotifier { /// Transforms passed value before assigning it into input. ValueTransform valueTransform; + /// Initial value - does not change after creating. + T? _initialValue; + TextEditingController? _textEditingController; final StringToTypeConverter _defaultConverter = StringToTypeConverter(converter: (x, _) => x as T); @@ -67,10 +69,12 @@ class GladeInput extends ChangeNotifier { bool _isPure; /// Input is in invalid state when there was conversion error. - bool _conversionError = false; + ConvertError? __conversionError; GladeModel? _bindedModel; + T? get initialValue => _initialValue; + TextEditingController? get controller => _textEditingController; T get value => _value; @@ -88,11 +92,11 @@ class GladeInput extends ChangeNotifier { bool get isUnchanged => valueComparator?.call(initialValue, value) ?? (value == initialValue); /// Input does not have conversion error nor validation error. - bool get isValid => !_conversionError && _validator(value).isValid; + bool get isValid => !hasConversionError && _validator(value).isValid; bool get isNotValid => !isValid; - bool get hasConversionError => _conversionError; + bool get hasConversionError => __conversionError != null; /// String representattion of [value]. String get stringValue => stringTovalueConverter?.convertBack(value) ?? value.toString(); @@ -110,7 +114,7 @@ class GladeInput extends ChangeNotifier { ); _isPure = false; - _conversionError = false; + __conversionError = null; // propagate input's changes onChange?.call( @@ -128,23 +132,32 @@ class GladeInput extends ChangeNotifier { notifyListeners(); } + // ignore: avoid_setters_without_getters, ok for internal use + set _conversionError(ConvertError value) { + __conversionError = value; + _bindedModel?.notifyInputUpdated(this); + } + GladeInput({ required T value, required this.validatorInstance, required bool isPure, - required this.initialValue, required this.valueComparator, required this.inputKey, required this.translateError, required this.stringTovalueConverter, - required this.dependenciesFactory, + required InputDependenciesFactory? dependenciesFactory, required this.defaultTranslations, required this.onChange, - required this.valueTransform, + required ValueTransform? valueTransform, + T? initialValue, TextEditingController? textEditingController, bool createTextController = true, }) : _isPure = isPure, _value = value, + _initialValue = initialValue, + dependenciesFactory = dependenciesFactory ?? (() => []), + valueTransform = valueTransform ?? _defaultTransform, _textEditingController = textEditingController ?? (createTextController ? TextEditingController( @@ -158,72 +171,10 @@ class GladeInput extends ChangeNotifier { validatorInstance.bindInput(this); } - GladeInput.pure( - T value, { - required String inputKey, - T? initialValue, - ValueComparator? valueComparator, - StringToTypeConverter? valueConverter, - ValidatorInstance? validatorInstance, - InputDependenciesFactory? dependencies, - ErrorTranslator? translateError, - DefaultTranslations? defaultTranslations, - OnChange? onChange, - TextEditingController? textEditingController, - bool createTextController = true, - ValueTransform? valueTransform, - }) : this( - value: value, - isPure: true, - inputKey: inputKey, - initialValue: initialValue ?? value, - valueComparator: valueComparator, - stringTovalueConverter: valueConverter, - dependenciesFactory: dependencies ?? () => [], - validatorInstance: validatorInstance ?? GladeValidator().build(), - translateError: translateError, - defaultTranslations: defaultTranslations, - onChange: onChange, - textEditingController: textEditingController, - createTextController: createTextController, - valueTransform: valueTransform ?? _defaultTransform, - ); - - GladeInput.dirty( - T value, { - required String inputKey, - T? initialValue, - ValueComparator? valueComparator, - StringToTypeConverter? valueConverter, - ValidatorInstance? validatorInstance, - InputDependenciesFactory? dependencies, - ErrorTranslator? translateError, - DefaultTranslations? defaultTranslations, - OnChange? onChange, - TextEditingController? textEditingController, - bool createTextController = true, - ValueTransform? valueTransform, - }) : this( - value: value, - isPure: false, - inputKey: inputKey, - initialValue: initialValue, - valueComparator: valueComparator, - stringTovalueConverter: valueConverter, - dependenciesFactory: dependencies ?? () => [], - validatorInstance: validatorInstance ?? GladeValidator().build(), - translateError: translateError, - defaultTranslations: defaultTranslations, - onChange: onChange, - textEditingController: textEditingController, - createTextController: createTextController, - valueTransform: valueTransform ?? _defaultTransform, - ); - factory GladeInput.create({ /// Sets current value of input. required T value, - required String inputKey, + String? inputKey, ValidatorFactory? validator, /// Initial value when GenericInput is created. @@ -232,45 +183,33 @@ class GladeInput extends ChangeNotifier { T? initialValue, bool pure = true, ErrorTranslator? translateError, - ValueComparator? comparator, + ValueComparator? valueComparator, StringToTypeConverter? valueConverter, InputDependenciesFactory? dependencies, OnChange? onChange, TextEditingController? textEditingController, bool createTextController = true, ValueTransform? valueTransform, + DefaultTranslations? defaultTranslations, }) { final validatorInstance = validator?.call(GladeValidator()) ?? GladeValidator().build(); - return pure - ? GladeInput.pure( - value, - validatorInstance: validatorInstance, - initialValue: initialValue ?? value, - translateError: translateError, - valueComparator: comparator, - inputKey: inputKey, - valueConverter: valueConverter, - dependencies: dependencies, - onChange: onChange, - textEditingController: textEditingController, - createTextController: createTextController, - valueTransform: valueTransform, - ) - : GladeInput.dirty( - value, - validatorInstance: validatorInstance, - initialValue: initialValue, - translateError: translateError, - valueComparator: comparator, - inputKey: inputKey, - valueConverter: valueConverter, - dependencies: dependencies, - onChange: onChange, - textEditingController: textEditingController, - createTextController: createTextController, - valueTransform: valueTransform, - ); + return GladeInput( + value: value, + isPure: pure, + validatorInstance: validatorInstance, + initialValue: initialValue, + translateError: translateError, + valueComparator: valueComparator, + inputKey: inputKey ?? '__${T.runtimeType}__${Random().nextInt(100)}', + stringTovalueConverter: valueConverter, + dependenciesFactory: dependencies, + onChange: onChange, + textEditingController: textEditingController, + createTextController: createTextController, + valueTransform: valueTransform, + defaultTranslations: defaultTranslations, + ); } // Predefined GenericInput without any validations. @@ -284,7 +223,7 @@ class GladeInput extends ChangeNotifier { T? initialValue, bool pure = true, ErrorTranslator? translateError, - ValueComparator? comparator, + ValueComparator? valueComparator, StringToTypeConverter? valueConverter, InputDependenciesFactory? dependencies, OnChange? onChange, @@ -297,7 +236,7 @@ class GladeInput extends ChangeNotifier { value: value, initialValue: initialValue, translateError: translateError, - comparator: comparator, + valueComparator: valueComparator, valueConverter: valueConverter, inputKey: inputKey, pure: pure, @@ -317,7 +256,7 @@ class GladeInput extends ChangeNotifier { T? initialValue, bool pure = true, ErrorTranslator? translateError, - ValueComparator? comparator, + ValueComparator? valueComparator, StringToTypeConverter? valueConverter, InputDependenciesFactory? dependencies, OnChange? onChange, @@ -330,7 +269,7 @@ class GladeInput extends ChangeNotifier { value: value, initialValue: initialValue, translateError: translateError, - comparator: comparator, + valueComparator: valueComparator, valueConverter: valueConverter, inputKey: inputKey, pure: pure, @@ -352,7 +291,7 @@ class GladeInput extends ChangeNotifier { int? initialValue, bool pure = true, ErrorTranslator? translateError, - ValueComparator? comparator, + ValueComparator? valueComparator, InputDependenciesFactory? dependencies, OnChange? onChange, TextEditingController? textEditingController, @@ -365,7 +304,7 @@ class GladeInput extends ChangeNotifier { validator: validator, pure: pure, translateError: translateError, - comparator: comparator, + valueComparator: valueComparator, inputKey: inputKey, dependencies: dependencies, valueConverter: GladeTypeConverters.intConverter, @@ -382,7 +321,7 @@ class GladeInput extends ChangeNotifier { bool? initialValue, bool pure = true, ErrorTranslator? translateError, - ValueComparator? comparator, + ValueComparator? valueComparator, InputDependenciesFactory? dependencies, OnChange? onChange, TextEditingController? textEditingController, @@ -395,7 +334,7 @@ class GladeInput extends ChangeNotifier { validator: validator, pure: pure, translateError: translateError, - comparator: comparator, + valueComparator: valueComparator, inputKey: inputKey, dependencies: dependencies, valueConverter: GladeTypeConverters.boolConverter, @@ -418,47 +357,40 @@ class GladeInput extends ChangeNotifier { TextEditingController? textEditingController, bool createTextController = true, bool isRequired = true, + ValueTransform? valueTransform, + ValueComparator? valueComparator, }) { final requiredInstance = validator?.call(StringValidator()..notEmpty()) ?? (StringValidator()..notEmpty()).build(); final optionalInstance = validator?.call(StringValidator()) ?? StringValidator().build(); - return pure - ? GladeInput.pure( - value, - initialValue: initialValue, - validatorInstance: isRequired ? requiredInstance : optionalInstance, - translateError: translateError, - defaultTranslations: defaultTranslations, - inputKey: inputKey, - dependencies: dependencies, - onChange: onChange, - textEditingController: textEditingController, - createTextController: createTextController, - ) - : GladeInput.dirty( - value, - initialValue: initialValue, - validatorInstance: isRequired ? requiredInstance : optionalInstance, - translateError: translateError, - inputKey: inputKey, - defaultTranslations: defaultTranslations, - dependencies: dependencies, - onChange: onChange, - textEditingController: textEditingController, - createTextController: createTextController, - ); + return GladeInput( + value: value, + isPure: pure, + initialValue: initialValue, + validatorInstance: isRequired ? requiredInstance : optionalInstance, + translateError: translateError, + defaultTranslations: defaultTranslations, + inputKey: inputKey, + dependenciesFactory: dependencies, + onChange: onChange, + textEditingController: textEditingController, + createTextController: createTextController, + valueComparator: valueComparator, + stringTovalueConverter: null, + valueTransform: valueTransform, + ); } - GladeInput asDirty(T value) => copyWith(isPure: false, value: value); - - GladeInput asPure(T value) => copyWith(isPure: true, value: value); - ValidatorResult validate() => _validator(value); String? translate({String delimiter = '.'}) => _translate(delimiter: delimiter, customError: validatorResult); - String errorFormatted({String delimiter = '|'}) => - validatorResult.isInvalid ? validatorResult.errors.map((e) => e.toString()).join(delimiter) : ''; + String errorFormatted({String delimiter = '|'}) { + // ignore: avoid-non-null-assertion, it is not null + if (hasConversionError) return _translateConversionError(__conversionError!); + + return validatorResult.isInvalid ? validatorResult.errors.map((e) => e.toString()).join(delimiter) : ''; + } /// Shorthand validator for TextFieldForm inputs. /// @@ -507,14 +439,33 @@ class GladeInput extends ChangeNotifier { try { this.value = converter.convert(strValue); - } on ConvertError { - _conversionError = true; + } on ConvertError catch (conversionError) { + _conversionError = conversionError; } } // ignore: use_setters_to_change_properties, used as shorthand for field setter. void updateValue(T value) => this.value = value; + /// Resets input into pure state. + /// + /// Allows to sets new initialValue and value if needed. + /// By default ([invokeUpdate]=`true`) setting value will trigger listeners. + void resetToPure({ValueGetter? value, ValueGetter? initialValue, bool invokeUpdate = true}) { + this._isPure = true; + if (value != null) { + if (invokeUpdate) { + this.value = value(); + } else { + _value = value(); + } + } + + if (initialValue != null) { + this._initialValue = initialValue(); + } + } + @protected GladeInput copyWith({ String? inputKey, @@ -561,13 +512,7 @@ class GladeInput extends ChangeNotifier { } if (err is ConvertError) { - final defaultTranslationsTmp = this.defaultTranslations; - final translateErrorTmp = translateError; - if (translateErrorTmp != null) { - return translateErrorTmp(err, err.key, err.devErrorMessage, dependenciesFactory()); - } else if (defaultTranslationsTmp != null && defaultTranslationsTmp.defaultConversionMessage != null) { - return defaultTranslationsTmp.defaultConversionMessage; - } + return _translateConversionError(err); } //ignore: avoid-dynamic, ok for now @@ -578,6 +523,20 @@ class GladeInput extends ChangeNotifier { return err.toString(); } + String _translateConversionError(ConvertError err) { + final defaultTranslationsTmp = this.defaultTranslations; + final translateErrorTmp = translateError; + final defaultConversionMessage = defaultTranslationsTmp?.defaultConversionMessage; + + if (translateErrorTmp != null) { + return translateErrorTmp(err, err.key, err.devErrorMessage, dependenciesFactory()); + } else if (defaultConversionMessage != null) { + return defaultConversionMessage; + } + + return err.devErrorMessage; + } + ValidatorResult _validator(T value) { return validatorInstance.validate(value); } From b364c73f59cd6009b82002a0bd3cc52dfb4bc652 Mon Sep 17 00:00:00 2001 From: Petr Nymsa Date: Wed, 1 Nov 2023 11:55:46 +0100 Subject: [PATCH 6/6] Move default inputKey value into default constructor --- glade_forms/lib/src/core/glade_input.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/glade_forms/lib/src/core/glade_input.dart b/glade_forms/lib/src/core/glade_input.dart index 51c9b1d..12748f5 100644 --- a/glade_forms/lib/src/core/glade_input.dart +++ b/glade_forms/lib/src/core/glade_input.dart @@ -143,7 +143,7 @@ class GladeInput extends ChangeNotifier { required this.validatorInstance, required bool isPure, required this.valueComparator, - required this.inputKey, + required String? inputKey, required this.translateError, required this.stringTovalueConverter, required InputDependenciesFactory? dependenciesFactory, @@ -157,6 +157,7 @@ class GladeInput extends ChangeNotifier { _value = value, _initialValue = initialValue, dependenciesFactory = dependenciesFactory ?? (() => []), + inputKey = inputKey ?? '__${T.runtimeType}__${Random().nextInt(100000000)}', valueTransform = valueTransform ?? _defaultTransform, _textEditingController = textEditingController ?? (createTextController @@ -201,7 +202,7 @@ class GladeInput extends ChangeNotifier { initialValue: initialValue, translateError: translateError, valueComparator: valueComparator, - inputKey: inputKey ?? '__${T.runtimeType}__${Random().nextInt(100)}', + inputKey: inputKey, stringTovalueConverter: valueConverter, dependenciesFactory: dependencies, onChange: onChange, @@ -219,7 +220,7 @@ class GladeInput extends ChangeNotifier { /// In case of need of any validation use [GladeInput.create] directly. factory GladeInput.optional({ required T value, - required String inputKey, + String? inputKey, T? initialValue, bool pure = true, ErrorTranslator? translateError, @@ -252,7 +253,7 @@ class GladeInput extends ChangeNotifier { /// In case of need of any aditional validation use [GladeInput.create] directly. factory GladeInput.required({ required T value, - required String inputKey, + String? inputKey, T? initialValue, bool pure = true, ErrorTranslator? translateError, @@ -286,7 +287,7 @@ class GladeInput extends ChangeNotifier { static GladeInput intInput({ required int value, - required String inputKey, + String? inputKey, ValidatorFactory? validator, int? initialValue, bool pure = true, @@ -316,7 +317,7 @@ class GladeInput extends ChangeNotifier { static GladeInput boolInput({ required bool value, - required String inputKey, + String? inputKey, ValidatorFactory? validator, bool? initialValue, bool pure = true, @@ -345,7 +346,7 @@ class GladeInput extends ChangeNotifier { ); static GladeInput stringInput({ - required String inputKey, + String? inputKey, String? value, StringValidatorFactory? validator, String? initialValue,