Skip to content

Commit fc53c71

Browse files
authored
Fixes initial validation with AutovalidateMode.always on first build (#156708)
Fixes #142701 This PR fixes an issue where on the first build `AutovalidateMode.always` was not called. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent 56cfef7 commit fc53c71

File tree

4 files changed

+60
-17
lines changed

4 files changed

+60
-17
lines changed

packages/flutter/lib/src/widgets/form.dart

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:flutter/foundation.dart';
1212
import 'package:flutter/rendering.dart';
1313

1414
import 'basic.dart';
15+
import 'binding.dart';
1516
import 'focus_manager.dart';
1617
import 'focus_scope.dart';
1718
import 'framework.dart';
@@ -250,22 +251,10 @@ class FormState extends State<Form> {
250251

251252
void _register(FormFieldState<dynamic> field) {
252253
_fields.add(field);
253-
if (widget.autovalidateMode == AutovalidateMode.onUnfocus) {
254-
field._focusNode.addListener(() => _updateField(field));
255-
}
256254
}
257255

258256
void _unregister(FormFieldState<dynamic> field) {
259257
_fields.remove(field);
260-
if (widget.autovalidateMode == AutovalidateMode.onUnfocus) {
261-
field._focusNode.removeListener(()=> _updateField(field));
262-
}
263-
}
264-
265-
void _updateField(FormFieldState<dynamic> field) {
266-
if (!field._focusNode.hasFocus) {
267-
_validate();
268-
}
269258
}
270259

271260
@protected
@@ -703,6 +692,25 @@ class FormFieldState<T> extends State<FormField<T>> with RestorationMixin {
703692
}
704693

705694
@protected
695+
@override
696+
void didChangeDependencies() {
697+
super.didChangeDependencies();
698+
switch (Form.maybeOf(context)?.widget.autovalidateMode) {
699+
case AutovalidateMode.always:
700+
WidgetsBinding.instance.addPostFrameCallback((_) {
701+
// If the form is already validated, don't validate again.
702+
if (widget.enabled && !hasError && !isValid) {
703+
validate();
704+
}
705+
});
706+
case AutovalidateMode.onUnfocus:
707+
case AutovalidateMode.onUserInteraction:
708+
case AutovalidateMode.disabled:
709+
case null:
710+
break;
711+
}
712+
}
713+
706714
@override
707715
void dispose() {
708716
_errorText.dispose();
@@ -749,7 +757,6 @@ class FormFieldState<T> extends State<FormField<T>> with RestorationMixin {
749757

750758
return widget.builder(this);
751759
}
752-
753760
}
754761

755762
/// Used to configure the auto validation of [FormField] and [Form] widgets.

packages/flutter/test/material/time_picker_test.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2074,7 +2074,6 @@ void main() {
20742074
// Enter invalid hour.
20752075
await tester.enterText(find.byType(TextField).first, 'AB');
20762076
await tester.tap(find.text(okString));
2077-
await tester.pumpAndSettle();
20782077

20792078
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
20802079
});
@@ -2093,7 +2092,6 @@ void main() {
20932092
// Enter invalid hour.
20942093
await tester.enterText(find.byType(TextField).first, 'AB');
20952094
await tester.tap(find.text(okString));
2096-
await tester.pumpAndSettle();
20972095

20982096
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
20992097
});

packages/flutter/test/material/time_picker_theme_test.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,6 @@ void main() {
872872
// Enter invalid hour.
873873
await tester.enterText(find.byType(TextField).first, 'AB');
874874
await tester.tap(find.text('OK'));
875-
await tester.pumpAndSettle();
876875

877876
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 72.0));
878877
});
@@ -890,7 +889,6 @@ void main() {
890889
// Enter invalid hour.
891890
await tester.enterText(find.byType(TextField).first, 'AB');
892891
await tester.tap(find.text('OK'));
893-
await tester.pumpAndSettle();
894892

895893
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
896894
});

packages/flutter/test/widgets/form_test.dart

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,4 +1457,44 @@ void main() {
14571457

14581458
expect(focusNode2.hasFocus, isTrue);
14591459
});
1460+
1461+
testWidgets('AutovalidateMode.always should validate on second build', (WidgetTester tester) async {
1462+
String errorText(String? value) => '$value/error';
1463+
1464+
await tester.pumpWidget(
1465+
MaterialApp(
1466+
theme: ThemeData(),
1467+
home: Center(
1468+
child: Form(
1469+
autovalidateMode: AutovalidateMode.always,
1470+
child: Material(
1471+
child: Column(
1472+
children: <Widget>[
1473+
TextFormField(
1474+
initialValue: 'foo',
1475+
validator: errorText,
1476+
),
1477+
TextFormField(
1478+
initialValue: 'bar',
1479+
validator: errorText,
1480+
),
1481+
],
1482+
),
1483+
),
1484+
),
1485+
),
1486+
),
1487+
);
1488+
1489+
// The validation happens in a post frame callback, so the error
1490+
// doesn't show up until the second frame.
1491+
expect(find.text(errorText('foo')), findsNothing);
1492+
expect(find.text(errorText('bar')), findsNothing);
1493+
1494+
await tester.pump();
1495+
1496+
// The error shows up on the second frame.
1497+
expect(find.text(errorText('foo')), findsOneWidget);
1498+
expect(find.text(errorText('bar')), findsOneWidget);
1499+
});
14601500
}

0 commit comments

Comments
 (0)