Skip to content

Commit 042225a

Browse files
Merge pull request #1460 from flutter-form-builder-ecosystem/feature/1458-improve-autovalidate-modes
feat: #1458 improve autovalidate modes
2 parents 81b5f5e + 1dbe413 commit 042225a

File tree

4 files changed

+192
-10
lines changed

4 files changed

+192
-10
lines changed

lib/src/form_builder.dart

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

45
/// A container for form fields.
56
class FormBuilder extends StatefulWidget {
@@ -357,6 +358,18 @@ class FormBuilderState extends State<FormBuilder> {
357358
});
358359
}
359360

361+
@override
362+
void initState() {
363+
// Verify if need auto validate form
364+
if (enabled && (widget.autovalidateMode?.isAlways ?? false)) {
365+
WidgetsBinding.instance.addPostFrameCallback((_) {
366+
// No focus on invalid, like default behavior on Flutter base Form
367+
validate(focusOnInvalid: false);
368+
});
369+
}
370+
super.initState();
371+
}
372+
360373
@override
361374
Widget build(BuildContext context) {
362375
return Form(

lib/src/form_builder_field.dart

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'package:flutter/widgets.dart';
22
import 'package:flutter_form_builder/flutter_form_builder.dart';
3-
import 'package:flutter_form_builder/src/extensions/autovalidatemode_extension.dart';
43

54
enum OptionsOrientation { horizontal, vertical, wrap, auto }
65

@@ -98,16 +97,13 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
9897
bool get hasError => super.hasError || errorText != null;
9998

10099
@override
101-
bool get isValid => super.isValid && errorText == null;
100+
bool get isValid => super.isValid && _customErrorText == null;
102101

103102
bool get valueIsValid => super.isValid;
104103
bool get valueHasError => super.hasError;
105104

106105
bool get enabled => widget.enabled && (_formBuilderState?.enabled ?? true);
107106
bool get readOnly => !(_formBuilderState?.widget.skipDisabled ?? false);
108-
bool get _isAlwaysValidate =>
109-
widget.autovalidateMode.isAlways ||
110-
(_formBuilderState?.widget.autovalidateMode?.isAlways ?? false);
111107

112108
/// Will be true if the field is dirty
113109
///
@@ -140,11 +136,6 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
140136
focusAttachment = effectiveFocusNode.attach(context);
141137

142138
// Verify if need auto validate form
143-
if ((enabled || readOnly) && _isAlwaysValidate) {
144-
WidgetsBinding.instance.addPostFrameCallback((_) {
145-
validate();
146-
});
147-
}
148139
}
149140

150141
@override

test/src/form_builder_field_test.dart

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

@@ -181,6 +182,21 @@ void main() {
181182

182183
expect(find.text(errorTextField), findsOneWidget);
183184
});
185+
testWidgets(
186+
'Should not show error when init form and AutovalidateMode is disabled',
187+
(tester) async {
188+
const textFieldName = 'text4';
189+
const errorTextField = 'error text field';
190+
final testWidget = FormBuilderTextField(
191+
name: textFieldName,
192+
autovalidateMode: AutovalidateMode.disabled,
193+
validator: (value) => errorTextField,
194+
);
195+
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
196+
await tester.pumpAndSettle();
197+
198+
expect(find.text(errorTextField), findsNothing);
199+
});
184200
testWidgets(
185201
'Should show error when AutovalidateMode is onUserInteraction and change field',
186202
(tester) async {
@@ -199,6 +215,47 @@ void main() {
199215

200216
expect(find.text(errorTextField), findsOneWidget);
201217
});
218+
testWidgets(
219+
'Should show error when init form and AutovalidateMode is onUnfocus',
220+
(tester) async {
221+
const textFieldName = 'text4';
222+
const errorTextField = 'error text field';
223+
final testWidget = FormBuilderTextField(
224+
name: textFieldName,
225+
autovalidateMode: AutovalidateMode.onUnfocus,
226+
validator: (value) => errorTextField,
227+
);
228+
final widgetFinder = find.byWidget(testWidget);
229+
230+
// Init form
231+
await tester.pumpWidget(buildTestableFieldWidget(
232+
Column(
233+
children: [
234+
testWidget,
235+
ElevatedButton(onPressed: () {}, child: const Text('Submit')),
236+
],
237+
),
238+
autovalidateMode: AutovalidateMode.onUnfocus,
239+
));
240+
await tester.pumpAndSettle();
241+
final focusNode =
242+
formKey.currentState?.fields[textFieldName]?.effectiveFocusNode;
243+
expect(find.text(errorTextField), findsNothing);
244+
expect(Focus.of(tester.element(widgetFinder)).hasFocus, false);
245+
expect(focusNode?.hasFocus, false);
246+
247+
// Focus input and write text
248+
await tester.enterText(widgetFinder, 'test');
249+
await tester.pumpAndSettle();
250+
expect(Focus.of(tester.element(widgetFinder)).hasFocus, true);
251+
expect(focusNode?.hasFocus, true);
252+
expect(find.text(errorTextField), findsNothing);
253+
254+
// Unfocus input and show error
255+
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
256+
await tester.pumpAndSettle();
257+
expect(find.text(errorTextField), findsOneWidget);
258+
});
202259
});
203260
group('isDirty - ', () {
204261
testWidgets('Should not dirty by default', (tester) async {

test/src/form_builder_test.dart

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

@@ -249,6 +250,126 @@ void main() {
249250

250251
expect(find.text(errorTextField), findsOneWidget);
251252
});
253+
testWidgets(
254+
'Should not show error when init form and AutovalidateMode is disabled',
255+
(tester) async {
256+
const textFieldName = 'text4';
257+
const errorTextField = 'error text field';
258+
final testWidget = FormBuilderTextField(
259+
name: textFieldName,
260+
validator: (value) => errorTextField,
261+
);
262+
await tester.pumpWidget(buildTestableFieldWidget(
263+
testWidget,
264+
autovalidateMode: AutovalidateMode.disabled,
265+
));
266+
await tester.pumpAndSettle();
267+
268+
expect(find.text(errorTextField), findsNothing);
269+
});
270+
testWidgets(
271+
'Should show error when init form and AutovalidateMode is onUserInteraction',
272+
(tester) async {
273+
const textFieldName = 'text4';
274+
const errorTextField = 'error text field';
275+
final testWidget = FormBuilderTextField(
276+
name: textFieldName,
277+
validator: (value) => errorTextField,
278+
);
279+
await tester.pumpWidget(buildTestableFieldWidget(
280+
testWidget,
281+
autovalidateMode: AutovalidateMode.onUserInteraction,
282+
));
283+
await tester.pumpAndSettle();
284+
285+
expect(find.text(errorTextField), findsNothing);
286+
287+
final widgetFinder = find.byWidget(testWidget);
288+
await tester.enterText(widgetFinder, 'test');
289+
await tester.pumpAndSettle();
290+
291+
expect(find.text(errorTextField), findsOneWidget);
292+
});
293+
testWidgets(
294+
'Should show error when init form and AutovalidateMode is onUnfocus',
295+
(tester) async {
296+
const textFieldName = 'text4';
297+
const errorTextField = 'error text field';
298+
final testWidget = FormBuilderTextField(
299+
name: textFieldName,
300+
validator: (value) => errorTextField,
301+
);
302+
final widgetFinder = find.byWidget(testWidget);
303+
304+
// Init form
305+
await tester.pumpWidget(buildTestableFieldWidget(
306+
Column(
307+
children: [
308+
testWidget,
309+
ElevatedButton(onPressed: () {}, child: const Text('Submit')),
310+
],
311+
),
312+
autovalidateMode: AutovalidateMode.onUnfocus,
313+
));
314+
await tester.pumpAndSettle();
315+
final focusNode =
316+
formKey.currentState?.fields[textFieldName]?.effectiveFocusNode;
317+
expect(find.text(errorTextField), findsNothing);
318+
expect(Focus.of(tester.element(widgetFinder)).hasFocus, false);
319+
expect(focusNode?.hasFocus, false);
320+
321+
// Focus input and write text
322+
await tester.enterText(widgetFinder, 'test');
323+
await tester.pumpAndSettle();
324+
expect(Focus.of(tester.element(widgetFinder)).hasFocus, true);
325+
expect(focusNode?.hasFocus, true);
326+
expect(find.text(errorTextField), findsNothing);
327+
328+
// Unfocus input and show error
329+
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
330+
await tester.pumpAndSettle();
331+
expect(find.text(errorTextField), findsOneWidget);
332+
});
333+
group('Interact with FormBuilderField -', () {
334+
testWidgets(
335+
'Should show error when FormBuilder is disabled and FormBuilderField is always',
336+
(tester) async {
337+
const textFieldName = 'text4';
338+
const errorTextField = 'error text field';
339+
final testWidget = FormBuilderTextField(
340+
name: textFieldName,
341+
validator: (value) => errorTextField,
342+
autovalidateMode: AutovalidateMode.always,
343+
);
344+
await tester.pumpWidget(buildTestableFieldWidget(
345+
testWidget,
346+
autovalidateMode: AutovalidateMode.disabled,
347+
));
348+
await tester.pumpAndSettle();
349+
350+
expect(find.text(errorTextField), findsOneWidget);
351+
});
352+
// TODO: Enable when solve issue
353+
// https://github.com/flutter/flutter/issues/125766
354+
// testWidgets(
355+
// 'Should not show error when FormBuilder is always and FormBuilderField is disabled',
356+
// (tester) async {
357+
// const textFieldName = 'text4';
358+
// const errorTextField = 'error text field';
359+
// final testWidget = FormBuilderTextField(
360+
// name: textFieldName,
361+
// validator: (value) => errorTextField,
362+
// autovalidateMode: AutovalidateMode.disabled,
363+
// );
364+
// await tester.pumpWidget(buildTestableFieldWidget(
365+
// testWidget,
366+
// autovalidateMode: AutovalidateMode.always,
367+
// ));
368+
// await tester.pumpAndSettle();
369+
370+
// expect(find.text(errorTextField), findsNothing);
371+
// });
372+
});
252373
});
253374

254375
group('isDirty - ', () {

0 commit comments

Comments
 (0)