Skip to content

Commit 76a1074

Browse files
Merge pull request #1232 from flutter-form-builder-ecosystem/improve-autovalidate-modes
Improve autovalidate modes
2 parents f5a831b + 0e0f0e8 commit 76a1074

File tree

6 files changed

+212
-38
lines changed

6 files changed

+212
-38
lines changed

example/android/build.gradle

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ buildscript {
22
ext.kotlin_version = '1.6.10'
33
repositories {
44
google()
5-
jcenter()
5+
mavenCentral()
66
}
77

88
dependencies {
@@ -14,15 +14,13 @@ buildscript {
1414
allprojects {
1515
repositories {
1616
google()
17-
jcenter()
17+
mavenCentral()
1818
}
1919
}
2020

2121
rootProject.buildDir = '../build'
2222
subprojects {
2323
project.buildDir = "${rootProject.buildDir}/${project.name}"
24-
}
25-
subprojects {
2624
project.evaluationDependsOn(':app')
2725
}
2826

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import 'package:flutter/material.dart';
2+
3+
extension AutovalidateModeExtension on AutovalidateMode {
4+
/// Is always or is onUserInteraction
5+
bool get isEnable => isAlways || isOnUserInteraction;
6+
bool get isAlways => this == AutovalidateMode.always;
7+
bool get isOnUserInteraction => this == AutovalidateMode.onUserInteraction;
8+
}

lib/src/form_builder.dart

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:flutter/material.dart';
2+
import 'package:flutter_form_builder/src/extensions/autovalidatemode_extension.dart';
23
import 'package:flutter_form_builder/flutter_form_builder.dart';
34

45
/// A container for form fields.
@@ -151,23 +152,16 @@ class FormBuilderState extends State<FormBuilder> {
151152
initialValue[name];
152153
}
153154

154-
void setInternalFieldValue<T>(String name, T? value,
155-
{required bool isSetState}) {
155+
void setInternalFieldValue<T>(String name, T? value) {
156156
_instantValue[name] = value;
157-
if (isSetState) {
158-
setState(() {});
157+
if (widget.autovalidateMode?.isEnable ?? false) {
158+
validate();
159159
}
160160
widget.onChanged?.call();
161161
}
162162

163-
void removeInternalFieldValue(
164-
String name, {
165-
required bool isSetState,
166-
}) {
163+
void removeInternalFieldValue(String name) {
167164
_instantValue.remove(name);
168-
if (isSetState) {
169-
setState(() {});
170-
}
171165
}
172166

173167
void registerField(String name, FormBuilderFieldState field) {

lib/src/form_builder_field.dart

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_form_builder/flutter_form_builder.dart';
3+
import 'package:flutter_form_builder/src/extensions/autovalidatemode_extension.dart';
34

45
enum OptionsOrientation { horizontal, vertical, wrap }
56

@@ -51,7 +52,7 @@ class FormBuilderField<T> extends FormField<T> {
5152
super.key,
5253
super.onSaved,
5354
super.initialValue,
54-
super.autovalidateMode = AutovalidateMode.onUserInteraction,
55+
super.autovalidateMode,
5556
super.enabled = true,
5657
super.validator,
5758
super.restorationId,
@@ -106,6 +107,12 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
106107

107108
bool get enabled => widget.enabled && (_formBuilderState?.enabled ?? true);
108109
bool get _readOnly => !(_formBuilderState?.widget.skipDisabled ?? false);
110+
bool get _isEnableValidate =>
111+
widget.autovalidateMode.isEnable ||
112+
(_formBuilderState?.widget.autovalidateMode?.isEnable ?? false);
113+
bool get _isAlwaysValidate =>
114+
widget.autovalidateMode.isAlways ||
115+
(_formBuilderState?.widget.autovalidateMode?.isAlways ?? false);
109116

110117
/// Will be true if the field is dirty
111118
///
@@ -145,10 +152,7 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
145152
focusAttachment = effectiveFocusNode.attach(context);
146153

147154
// Verify if need auto validate form
148-
if ((enabled || _readOnly) &&
149-
(widget.autovalidateMode == AutovalidateMode.always ||
150-
_formBuilderState?.widget.autovalidateMode ==
151-
AutovalidateMode.always)) {
155+
if ((enabled || _readOnly) && _isAlwaysValidate) {
152156
WidgetsBinding.instance.addPostFrameCallback((_) {
153157
validate();
154158
});
@@ -186,17 +190,11 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
186190
if (_formBuilderState != null) {
187191
_dirty = true;
188192
if (enabled || _readOnly) {
189-
_formBuilderState!.setInternalFieldValue<T>(
190-
widget.name,
191-
value,
192-
isSetState: false,
193-
);
194-
} else {
195-
_formBuilderState!.removeInternalFieldValue(
196-
widget.name,
197-
isSetState: false,
198-
);
193+
_formBuilderState!.setInternalFieldValue<T>(widget.name, value);
194+
if (_isEnableValidate) validate();
195+
return;
199196
}
197+
_formBuilderState!.removeInternalFieldValue(widget.name);
200198
}
201199
}
202200

test/src/form_builder_field_test.dart

Lines changed: 121 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,106 @@ void main() {
2525
});
2626
});
2727

28+
group('isValid -', () {
29+
testWidgets('Should invalid when set custom error', (tester) async {
30+
final textFieldKey = GlobalKey<FormBuilderFieldState>();
31+
const textFieldName = 'text';
32+
const errorTextField = 'error text field';
33+
final testWidget = FormBuilderTextField(
34+
name: textFieldName,
35+
key: textFieldKey,
36+
);
37+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
38+
39+
// Set custom error
40+
textFieldKey.currentState?.invalidate(errorTextField);
41+
await tester.pumpAndSettle();
42+
43+
expect(textFieldKey.currentState?.isValid, isFalse);
44+
});
45+
testWidgets(
46+
'Should valid when no has error and autovalidateMode is always',
47+
(tester) async {
48+
final textFieldKey = GlobalKey<FormBuilderFieldState>();
49+
const textFieldName = 'text';
50+
const errorTextField = 'error text field';
51+
final testWidget = FormBuilderTextField(
52+
name: textFieldName,
53+
key: textFieldKey,
54+
autovalidateMode: AutovalidateMode.always,
55+
validator: (value) =>
56+
value == null || value.isEmpty ? errorTextField : null,
57+
);
58+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
59+
60+
expect(textFieldKey.currentState?.isValid, isFalse);
61+
62+
final widgetFinder = find.byWidget(testWidget);
63+
await tester.enterText(widgetFinder, 'test');
64+
await tester.pumpAndSettle();
65+
66+
expect(textFieldKey.currentState?.isValid, isTrue);
67+
});
68+
testWidgets(
69+
'Should invalid when has error and autovalidateMode is always',
70+
(tester) async {
71+
final textFieldKey = GlobalKey<FormBuilderFieldState>();
72+
const textFieldName = 'text';
73+
const errorTextField = 'error text field';
74+
final testWidget = FormBuilderTextField(
75+
name: textFieldName,
76+
key: textFieldKey,
77+
autovalidateMode: AutovalidateMode.always,
78+
validator: (value) =>
79+
value == null || value.length < 10 ? errorTextField : null,
80+
);
81+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
82+
83+
expect(textFieldKey.currentState?.isValid, isFalse);
84+
85+
final widgetFinder = find.byWidget(testWidget);
86+
await tester.enterText(widgetFinder, 'test');
87+
await tester.pumpAndSettle();
88+
89+
expect(textFieldKey.currentState?.isValid, isFalse);
90+
});
91+
});
92+
93+
group('hasErrors -', () {
94+
testWidgets('Should has errors when set custom error', (tester) async {
95+
final textFieldKey = GlobalKey<FormBuilderFieldState>();
96+
const textFieldName = 'text';
97+
const errorTextField = 'error text field';
98+
final testWidget = FormBuilderTextField(
99+
name: textFieldName,
100+
key: textFieldKey,
101+
);
102+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
103+
104+
// Set custom error
105+
textFieldKey.currentState?.invalidate(errorTextField);
106+
await tester.pumpAndSettle();
107+
108+
expect(textFieldKey.currentState?.hasError, isTrue);
109+
});
110+
testWidgets('Should no has errors when is empty and no has validators',
111+
(tester) async {
112+
final textFieldKey = GlobalKey<FormBuilderFieldState>();
113+
const textFieldName = 'text';
114+
final testWidget = FormBuilderTextField(
115+
name: textFieldName,
116+
key: textFieldKey,
117+
);
118+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
119+
120+
// Set custom error
121+
textFieldKey.currentState?.validate();
122+
await tester.pumpAndSettle();
123+
124+
expect(textFieldKey.currentState?.hasError, isFalse);
125+
});
126+
});
127+
28128
group('autovalidateMode -', () {
29129
testWidgets(
30130
'Should show error when init form and AutovalidateMode is always',
@@ -33,14 +133,29 @@ void main() {
33133
const errorTextField = 'error text field';
34134
final testWidget = FormBuilderTextField(
35135
name: textFieldName,
36-
validator: (value) => errorTextField,
136+
validator: (value) =>
137+
value == null || value.isEmpty ? errorTextField : null,
138+
autovalidateMode: AutovalidateMode.always,
37139
);
38-
await tester.pumpWidget(
39-
buildTestableFieldWidget(
40-
testWidget,
41-
autovalidateMode: AutovalidateMode.always,
42-
),
140+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
141+
await tester.pumpAndSettle();
142+
143+
expect(find.text(errorTextField), findsOneWidget);
144+
});
145+
testWidgets(
146+
'Should show error when AutovalidateMode is onUserInteraction and change field',
147+
(tester) async {
148+
const textFieldName = 'text4';
149+
const errorTextField = 'error text field';
150+
final testWidget = FormBuilderTextField(
151+
name: textFieldName,
152+
autovalidateMode: AutovalidateMode.onUserInteraction,
153+
validator: (value) => errorTextField,
43154
);
155+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
156+
expect(find.text(errorTextField), findsNothing);
157+
158+
await tester.enterText(find.byWidget(testWidget), 'hola');
44159
await tester.pumpAndSettle();
45160

46161
expect(find.text(errorTextField), findsOneWidget);

test/src/form_builder_test.dart

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,65 @@ void main() {
118118
);
119119
});
120120

121+
group('isValid -', () {
122+
testWidgets('Should invalid when set custom error', (tester) async {
123+
const textFieldName = 'text';
124+
const errorTextField = 'error text field';
125+
final testWidget = FormBuilderTextField(name: textFieldName);
126+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
127+
128+
// Set custom error
129+
formKey.currentState?.fields[textFieldName]?.invalidate(errorTextField);
130+
await tester.pumpAndSettle();
131+
132+
expect(formKey.currentState?.isValid, isFalse);
133+
});
134+
testWidgets('Should valid when no has error and autovalidateMode is always',
135+
(tester) async {
136+
const textFieldName = 'text';
137+
const errorTextField = 'error text field';
138+
final testWidget = FormBuilderTextField(
139+
name: textFieldName,
140+
validator: (value) =>
141+
value == null || value.isEmpty ? errorTextField : null,
142+
);
143+
await tester.pumpWidget(buildTestableFieldWidget(
144+
testWidget,
145+
autovalidateMode: AutovalidateMode.always,
146+
));
147+
148+
expect(formKey.currentState?.isValid, isFalse);
149+
150+
final widgetFinder = find.byWidget(testWidget);
151+
await tester.enterText(widgetFinder, 'test');
152+
await tester.pumpAndSettle();
153+
154+
expect(formKey.currentState?.isValid, isTrue);
155+
});
156+
testWidgets('Should invalid when has error and autovalidateMode is always',
157+
(tester) async {
158+
const textFieldName = 'text';
159+
const errorTextField = 'error text field';
160+
final testWidget = FormBuilderTextField(
161+
name: textFieldName,
162+
validator: (value) =>
163+
value == null || value.length < 10 ? errorTextField : null,
164+
);
165+
await tester.pumpWidget(buildTestableFieldWidget(
166+
testWidget,
167+
autovalidateMode: AutovalidateMode.always,
168+
));
169+
170+
expect(formKey.currentState?.isValid, isFalse);
171+
172+
final widgetFinder = find.byWidget(testWidget);
173+
await tester.enterText(widgetFinder, 'test');
174+
await tester.pumpAndSettle();
175+
176+
expect(formKey.currentState?.isValid, isFalse);
177+
});
178+
});
179+
121180
group('skipDisabled -', () {
122181
testWidgets(
123182
'Should not show error when field is not enabled and skipDisabled is true',
@@ -177,9 +236,11 @@ void main() {
177236
final testWidget = FormBuilderTextField(
178237
name: textFieldName,
179238
validator: (value) => errorTextField,
180-
autovalidateMode: AutovalidateMode.always,
181239
);
182-
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
240+
await tester.pumpWidget(buildTestableFieldWidget(
241+
testWidget,
242+
autovalidateMode: AutovalidateMode.always,
243+
));
183244
await tester.pumpAndSettle();
184245

185246
expect(find.text(errorTextField), findsOneWidget);

0 commit comments

Comments
 (0)