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
This commit is contained in:
Mairramer 2024-11-19 16:11:00 -03:00 committed by GitHub
parent 56cfef73fc
commit fc53c71381
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 60 additions and 17 deletions

View File

@ -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<Form> {
void _register(FormFieldState<dynamic> field) {
_fields.add(field);
if (widget.autovalidateMode == AutovalidateMode.onUnfocus) {
field._focusNode.addListener(() => _updateField(field));
}
}
void _unregister(FormFieldState<dynamic> field) {
_fields.remove(field);
if (widget.autovalidateMode == AutovalidateMode.onUnfocus) {
field._focusNode.removeListener(()=> _updateField(field));
}
}
void _updateField(FormFieldState<dynamic> field) {
if (!field._focusNode.hasFocus) {
_validate();
}
}
@protected
@ -703,6 +692,25 @@ class FormFieldState<T> extends State<FormField<T>> 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<T> extends State<FormField<T>> with RestorationMixin {
return widget.builder(this);
}
}
/// Used to configure the auto validation of [FormField] and [Form] widgets.

View File

@ -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));
});

View File

@ -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));
});

View File

@ -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: <Widget>[
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);
});
}