Skip to content

Commit 1d32486

Browse files
committed
Merge branch 'duplicate_field_handling'
# Conflicts: # packages/flutter_form_builder/lib/src/form_builder.dart # packages/flutter_form_builder/lib/src/form_builder_field.dart
2 parents 18e7ba5 + c0b879a commit 1d32486

20 files changed

+358
-46
lines changed

packages/flutter_form_builder/lib/src/fields/form_builder_date_time_picker.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ class _FormBuilderDateTimePickerState
260260
super.initState();
261261
_textFieldController = widget.controller ?? TextEditingController();
262262
_dateFormat = widget.format ?? _getDefaultDateTimeFormat();
263-
final initVal = initialValue;
263+
//setting this to value instead of initialValue here is OK since we handle initial value in the parent class
264+
final initVal = value;
264265
_textFieldController.text =
265266
initVal == null ? '' : _dateFormat.format(initVal);
266267
effectiveFocusNode.addListener(_handleFocus);

packages/flutter_form_builder/lib/src/fields/form_builder_text_field.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,8 @@ class _FormBuilderTextFieldState
435435
@override
436436
void initState() {
437437
super.initState();
438-
_controller =
439-
widget.controller ?? TextEditingController(text: initialValue);
438+
//setting this to value instead of initialValue here is OK since we handle initial value in the parent class
439+
_controller = widget.controller ?? TextEditingController(text: value);
440440
_controller!.addListener(_handleControllerChanged);
441441
}
442442

packages/flutter_form_builder/lib/src/form_builder.dart

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:developer';
2+
13
import 'package:flutter/material.dart';
24

35
import 'package:flutter_form_builder/flutter_form_builder.dart';
@@ -91,23 +93,60 @@ class FormBuilderState extends State<FormBuilder> {
9193

9294
final _fields = <String, FormBuilderFieldState>{};
9395

94-
final _value = <String, dynamic>{};
96+
//because dart type system will not accept ValueTransformer<dynamic>
97+
final _transformers = <String, Function>{};
98+
final _instantValue = <String, dynamic>{};
99+
final _savedValue = <String, dynamic>{};
100+
101+
Map<String, dynamic> get instantValue =>
102+
Map<String, dynamic>.unmodifiable(_instantValue.map((key, value) =>
103+
MapEntry(key, _transformers[key]?.call(value) ?? value)));
95104

96-
Map<String, dynamic> get value => Map<String, dynamic>.unmodifiable(_value);
105+
/// Returns the saved value only
106+
Map<String, dynamic> get value =>
107+
Map<String, dynamic>.unmodifiable(_savedValue.map((key, value) =>
108+
MapEntry(key, _transformers[key]?.call(value) ?? value)));
97109

110+
/// Returns values after saving
98111
Map<String, dynamic> get initialValue => widget.initialValue;
99112

100113
Map<String, FormBuilderFieldState> get fields => _fields;
101114

102-
bool get isValid =>
103-
fields.values.where((element) => !element.isValid).isEmpty;
115+
dynamic transformValue<T>(String name, T? v) {
116+
final t = _transformers[name];
117+
return t != null ? t.call(v) : v;
118+
}
119+
120+
dynamic getTransformedValue<T>(String name, {bool fromSaved = false}) {
121+
return transformValue<T>(name, getRawValue(name));
122+
}
123+
124+
T? getRawValue<T>(String name, {bool fromSaved = false}) {
125+
return (fromSaved ? _savedValue[name] : _instantValue[name]) ??
126+
initialValue[name];
127+
}
104128

105-
void setInternalFieldValue(String name, dynamic value) {
106-
setState(() => _value[name] = value);
129+
void setInternalFieldValue<T>(
130+
String name,
131+
T? value, {
132+
required bool isSetState,
133+
}) {
134+
_instantValue[name] = value;
135+
if (isSetState) {
136+
setState(() {});
137+
}
107138
}
139+
bool get isValid =>
140+
fields.values.where((element) => !element.isValid).isEmpty;
108141

109-
void removeInternalFieldValue(String name) {
110-
setState(() => _value.remove(name));
142+
void removeInternalFieldValue(
143+
String name, {
144+
required bool isSetState,
145+
}) {
146+
_instantValue.remove(name);
147+
if (isSetState) {
148+
setState(() {});
149+
}
111150
}
112151

113152
void registerField(String name, FormBuilderFieldState field) {
@@ -117,14 +156,30 @@ class FormBuilderState extends State<FormBuilder> {
117156
// field is being replaced, the new instance is registered before the old
118157
// one is unregistered. To accommodate that use case, but also provide
119158
// assistance to accidental duplicate names, we check and emit a warning.
159+
final oldField = _fields[name];
120160
assert(() {
121-
if (_fields.containsKey(name)) {
161+
if (oldField != null) {
122162
debugPrint('Warning! Replacing duplicate Field for $name'
123163
' -- this is OK to ignore as long as the field was intentionally replaced');
124164
}
125165
return true;
126166
}());
167+
127168
_fields[name] = field;
169+
field.registerTransformer(_transformers);
170+
if (oldField != null) {
171+
// ignore: invalid_use_of_protected_member
172+
field.setValue(
173+
oldField.value,
174+
populateForm: false,
175+
);
176+
} else {
177+
// ignore: invalid_use_of_protected_member
178+
field.setValue(
179+
_instantValue[name] ??= field.initialValue,
180+
populateForm: false,
181+
);
182+
}
128183
}
129184

130185
void unregisterField(String name, FormBuilderFieldState field) {
@@ -135,6 +190,7 @@ class FormBuilderState extends State<FormBuilder> {
135190
// since it may be intentional.
136191
if (field == _fields[name]) {
137192
_fields.remove(name);
193+
_transformers.remove(name);
138194
} else {
139195
assert(() {
140196
// This is OK to ignore when you are intentionally replacing a field
@@ -145,11 +201,14 @@ class FormBuilderState extends State<FormBuilder> {
145201
}());
146202
}
147203
// Removes internal field value
148-
_value.remove(name);
204+
// _savedValue.remove(name);
149205
}
150206

151207
void save() {
152208
_formKey.currentState!.save();
209+
//copy values from instant to saved
210+
_savedValue.clear();
211+
_savedValue.addAll(_instantValue);
153212
}
154213

155214
void invalidateField({required String name, String? errorText}) =>
@@ -174,7 +233,21 @@ class FormBuilderState extends State<FormBuilder> {
174233
}
175234

176235
void reset() {
236+
log('reset called');
177237
_formKey.currentState!.reset();
238+
for (var item in _fields.entries) {
239+
try {
240+
item.value.didChange(getRawValue(item.key));
241+
} catch (e, st) {
242+
log(
243+
'Error when resetting field: ${item.key}',
244+
error: e,
245+
stackTrace: st,
246+
level: 2000,
247+
);
248+
}
249+
}
250+
// _formKey.currentState!.setState(() {});
178251
}
179252

180253
void patchValue(Map<String, dynamic> val) {

packages/flutter_form_builder/lib/src/form_builder_field.dart

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ class FormBuilderField<T> extends FormField<T> {
7474
validator: validator,
7575
);
7676

77-
/*@override
78-
FormBuilderFieldState<T> createState();*/
7977
@override
8078
FormBuilderFieldState<FormBuilderField<T>, T> createState() =>
8179
FormBuilderFieldState<FormBuilderField<T>, T>();
@@ -100,6 +98,15 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
10098

10199
FormBuilderState? _formBuilderState;
102100

101+
dynamic get transformedValue => widget.valueTransformer?.call(value) ?? value;
102+
103+
void registerTransformer(Map<String, Function> _map) {
104+
final _fun = widget.valueTransformer;
105+
if (_fun != null) {
106+
_map[widget.name] = _fun;
107+
}
108+
}
109+
103110
@override
104111
String? get errorText => super.errorText ?? _customErrorText;
105112

@@ -115,20 +122,29 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
115122

116123
bool get enabled => widget.enabled && (_formBuilderState?.enabled ?? true);
117124

118-
FocusNode? _focusNode;
119-
FocusNode get effectiveFocusNode =>
120-
widget.focusNode ?? (_focusNode ??= FocusNode());
125+
late FocusNode effectiveFocusNode;
121126

122127
@override
123128
void initState() {
124129
super.initState();
125130
// Register this field when there is a parent FormBuilder
126131
_formBuilderState = FormBuilder.of(context);
132+
// Set the initial value
127133
_formBuilderState?.registerField(widget.name, this);
134+
135+
effectiveFocusNode = widget.focusNode ?? FocusNode(debugLabel: widget.name);
128136
// Register a touch handler
129137
effectiveFocusNode.addListener(_touchedHandler);
130-
// Set the initial value
131-
setValue(initialValue);
138+
}
139+
140+
@override
141+
void didUpdateWidget(covariant FormBuilderField<T> oldWidget) {
142+
super.didUpdateWidget(oldWidget);
143+
if (widget.focusNode != oldWidget.focusNode) {
144+
effectiveFocusNode.removeListener(_touchedHandler);
145+
effectiveFocusNode = widget.focusNode ?? FocusNode();
146+
effectiveFocusNode.addListener(_touchedHandler);
147+
}
132148
}
133149

134150
@override
@@ -142,17 +158,29 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
142158
super.dispose();
143159
}
144160

145-
@override
146-
void save() {
147-
super.save();
161+
// @override
162+
// void save() {
163+
// _informFormForFieldChange(
164+
// isSetState: true,
165+
// );
166+
// super.save();
167+
// }
168+
169+
void _informFormForFieldChange({
170+
required bool isSetState,
171+
}) {
148172
if (_formBuilderState != null) {
149173
if (enabled || !_formBuilderState!.widget.skipDisabled) {
150-
_formBuilderState!.setInternalFieldValue(
174+
_formBuilderState!.setInternalFieldValue<T>(
151175
widget.name,
152-
widget.valueTransformer?.call(value) ?? value,
176+
value,
177+
isSetState: isSetState,
153178
);
154179
} else {
155-
_formBuilderState!.removeInternalFieldValue(widget.name);
180+
_formBuilderState!.removeInternalFieldValue(
181+
widget.name,
182+
isSetState: isSetState,
183+
);
156184
}
157185
}
158186
}
@@ -163,9 +191,22 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
163191
}
164192
}
165193

194+
@override
195+
void setValue(T? value, {bool populateForm = true}) {
196+
super.setValue(value);
197+
if (populateForm) {
198+
_informFormForFieldChange(
199+
isSetState: false,
200+
);
201+
}
202+
}
203+
166204
@override
167205
void didChange(T? value) {
168206
super.didChange(value);
207+
_informFormForFieldChange(
208+
isSetState: false,
209+
);
169210
widget.onChanged?.call(value);
170211
}
171212

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# This file configures the analyzer, which statically analyzes Dart code to
2+
# check for errors, warnings, and lints.
3+
#
4+
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5+
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6+
# invoked from the command line by running `flutter analyze`.
7+
8+
# The following line activates a set of recommended lints for Flutter apps,
9+
# packages, and plugins designed to encourage good coding practices.
10+
include: package:flutter_lints/flutter.yaml
11+
12+
linter:
13+
# The lint rules applied to this project can be customized in the
14+
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
15+
# included above or to enable additional rules. A list of all available lints
16+
# and their documentation is published at
17+
# https://dart-lang.github.io/linter/lints/index.html.
18+
#
19+
# Instead of disabling a lint rule for the entire project in the
20+
# section below, it can also be suppressed for a single line of code
21+
# or a specific dart file by using the `// ignore: name_of_lint` and
22+
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
23+
# producing the lint.
24+
rules:
25+
# avoid_print: false # Uncomment to disable the `avoid_print` rule
26+
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27+
28+
# Additional information about this file can be found at
29+
# https://dart.dev/guides/language/analysis-options

packages/form_builder_extra_fields/example/lib/home_page.dart

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,31 @@ class _MyHomePageState extends State<MyHomePage> {
2828
autovalidateMode: AutovalidateMode.onUserInteraction,
2929
child: Column(
3030
children: [
31-
FormBuilderSearchableDropdown(
32-
name: 'searchable_dropdown',
31+
FormBuilderSearchableDropdown<String>(
32+
name: 'searchable_dropdown_online',
33+
// items: allCountries,
34+
onChanged: _onChanged,
35+
showSearchBox: true,
36+
isFilteredOnline: true,
37+
compareFn: (item, selectedItem) =>
38+
item.toLowerCase() == selectedItem.toLowerCase(),
39+
onFind: (text) async {
40+
await Future.delayed(const Duration(seconds: 1));
41+
return allCountries
42+
.where((element) =>
43+
element.toLowerCase().contains(text.toLowerCase()))
44+
.toList();
45+
},
46+
decoration: const InputDecoration(
47+
labelText: 'Searchable Dropdown Online'),
48+
),
49+
FormBuilderSearchableDropdown<String>(
50+
name: 'searchable_dropdown_offline',
3351
items: allCountries,
3452
onChanged: _onChanged,
35-
decoration:
36-
const InputDecoration(labelText: 'Searchable Dropdown'),
53+
showSearchBox: true,
54+
decoration: const InputDecoration(
55+
labelText: 'Searchable Dropdown Offline'),
3756
),
3857
const SizedBox(height: 15),
3958
FormBuilderColorPickerField(
917 Bytes
Loading
5.17 KB
Loading
8.06 KB
Loading
5.46 KB
Loading

0 commit comments

Comments
 (0)