diff --git a/packages/flutter/lib/src/widgets/form.dart b/packages/flutter/lib/src/widgets/form.dart index 7be29d6eaa..8fe5cf786b 100644 --- a/packages/flutter/lib/src/widgets/form.dart +++ b/packages/flutter/lib/src/widgets/form.dart @@ -12,6 +12,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'basic.dart'; +import 'binding.dart'; import 'focus_manager.dart'; import 'focus_scope.dart'; import 'framework.dart'; @@ -250,22 +251,10 @@ class FormState extends State
{ void _register(FormFieldState field) { _fields.add(field); - if (widget.autovalidateMode == AutovalidateMode.onUnfocus) { - field._focusNode.addListener(() => _updateField(field)); - } } void _unregister(FormFieldState field) { _fields.remove(field); - if (widget.autovalidateMode == AutovalidateMode.onUnfocus) { - field._focusNode.removeListener(()=> _updateField(field)); - } - } - - void _updateField(FormFieldState field) { - if (!field._focusNode.hasFocus) { - _validate(); - } } @protected @@ -703,6 +692,25 @@ class FormFieldState extends State> with RestorationMixin { } @protected + @override + void didChangeDependencies() { + super.didChangeDependencies(); + switch (Form.maybeOf(context)?.widget.autovalidateMode) { + case AutovalidateMode.always: + WidgetsBinding.instance.addPostFrameCallback((_) { + // If the form is already validated, don't validate again. + if (widget.enabled && !hasError && !isValid) { + validate(); + } + }); + case AutovalidateMode.onUnfocus: + case AutovalidateMode.onUserInteraction: + case AutovalidateMode.disabled: + case null: + break; + } + } + @override void dispose() { _errorText.dispose(); @@ -749,7 +757,6 @@ class FormFieldState extends State> with RestorationMixin { return widget.builder(this); } - } /// Used to configure the auto validation of [FormField] and [Form] widgets. diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart index 07b4339059..bbdbacc6ac 100644 --- a/packages/flutter/test/material/time_picker_test.dart +++ b/packages/flutter/test/material/time_picker_test.dart @@ -2074,7 +2074,6 @@ void main() { // Enter invalid hour. await tester.enterText(find.byType(TextField).first, 'AB'); await tester.tap(find.text(okString)); - await tester.pumpAndSettle(); expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0)); }); @@ -2093,7 +2092,6 @@ void main() { // Enter invalid hour. await tester.enterText(find.byType(TextField).first, 'AB'); await tester.tap(find.text(okString)); - await tester.pumpAndSettle(); expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0)); }); diff --git a/packages/flutter/test/material/time_picker_theme_test.dart b/packages/flutter/test/material/time_picker_theme_test.dart index 3251ddfbf4..3c7207380a 100644 --- a/packages/flutter/test/material/time_picker_theme_test.dart +++ b/packages/flutter/test/material/time_picker_theme_test.dart @@ -872,7 +872,6 @@ void main() { // Enter invalid hour. await tester.enterText(find.byType(TextField).first, 'AB'); await tester.tap(find.text('OK')); - await tester.pumpAndSettle(); expect(tester.getSize(findBorderPainter().first), const Size(96.0, 72.0)); }); @@ -890,7 +889,6 @@ void main() { // Enter invalid hour. await tester.enterText(find.byType(TextField).first, 'AB'); await tester.tap(find.text('OK')); - await tester.pumpAndSettle(); expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0)); }); diff --git a/packages/flutter/test/widgets/form_test.dart b/packages/flutter/test/widgets/form_test.dart index 9785d7c28d..5a3117d47d 100644 --- a/packages/flutter/test/widgets/form_test.dart +++ b/packages/flutter/test/widgets/form_test.dart @@ -1457,4 +1457,44 @@ void main() { expect(focusNode2.hasFocus, isTrue); }); + + testWidgets('AutovalidateMode.always should validate on second build', (WidgetTester tester) async { + String errorText(String? value) => '$value/error'; + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(), + home: Center( + child: Form( + autovalidateMode: AutovalidateMode.always, + child: Material( + child: Column( + children: [ + TextFormField( + initialValue: 'foo', + validator: errorText, + ), + TextFormField( + initialValue: 'bar', + validator: errorText, + ), + ], + ), + ), + ), + ), + ), + ); + + // The validation happens in a post frame callback, so the error + // doesn't show up until the second frame. + expect(find.text(errorText('foo')), findsNothing); + expect(find.text(errorText('bar')), findsNothing); + + await tester.pump(); + + // The error shows up on the second frame. + expect(find.text(errorText('foo')), findsOneWidget); + expect(find.text(errorText('bar')), findsOneWidget); + }); }