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:
parent
56cfef73fc
commit
fc53c71381
@ -12,6 +12,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
|
import 'binding.dart';
|
||||||
import 'focus_manager.dart';
|
import 'focus_manager.dart';
|
||||||
import 'focus_scope.dart';
|
import 'focus_scope.dart';
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
@ -250,22 +251,10 @@ class FormState extends State<Form> {
|
|||||||
|
|
||||||
void _register(FormFieldState<dynamic> field) {
|
void _register(FormFieldState<dynamic> field) {
|
||||||
_fields.add(field);
|
_fields.add(field);
|
||||||
if (widget.autovalidateMode == AutovalidateMode.onUnfocus) {
|
|
||||||
field._focusNode.addListener(() => _updateField(field));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unregister(FormFieldState<dynamic> field) {
|
void _unregister(FormFieldState<dynamic> field) {
|
||||||
_fields.remove(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
|
@protected
|
||||||
@ -703,6 +692,25 @@ class FormFieldState<T> extends State<FormField<T>> with RestorationMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
@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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_errorText.dispose();
|
_errorText.dispose();
|
||||||
@ -749,7 +757,6 @@ class FormFieldState<T> extends State<FormField<T>> with RestorationMixin {
|
|||||||
|
|
||||||
return widget.builder(this);
|
return widget.builder(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to configure the auto validation of [FormField] and [Form] widgets.
|
/// Used to configure the auto validation of [FormField] and [Form] widgets.
|
||||||
|
@ -2074,7 +2074,6 @@ void main() {
|
|||||||
// Enter invalid hour.
|
// Enter invalid hour.
|
||||||
await tester.enterText(find.byType(TextField).first, 'AB');
|
await tester.enterText(find.byType(TextField).first, 'AB');
|
||||||
await tester.tap(find.text(okString));
|
await tester.tap(find.text(okString));
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
|
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
|
||||||
});
|
});
|
||||||
@ -2093,7 +2092,6 @@ void main() {
|
|||||||
// Enter invalid hour.
|
// Enter invalid hour.
|
||||||
await tester.enterText(find.byType(TextField).first, 'AB');
|
await tester.enterText(find.byType(TextField).first, 'AB');
|
||||||
await tester.tap(find.text(okString));
|
await tester.tap(find.text(okString));
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
|
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
|
||||||
});
|
});
|
||||||
|
@ -872,7 +872,6 @@ void main() {
|
|||||||
// Enter invalid hour.
|
// Enter invalid hour.
|
||||||
await tester.enterText(find.byType(TextField).first, 'AB');
|
await tester.enterText(find.byType(TextField).first, 'AB');
|
||||||
await tester.tap(find.text('OK'));
|
await tester.tap(find.text('OK'));
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 72.0));
|
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 72.0));
|
||||||
});
|
});
|
||||||
@ -890,7 +889,6 @@ void main() {
|
|||||||
// Enter invalid hour.
|
// Enter invalid hour.
|
||||||
await tester.enterText(find.byType(TextField).first, 'AB');
|
await tester.enterText(find.byType(TextField).first, 'AB');
|
||||||
await tester.tap(find.text('OK'));
|
await tester.tap(find.text('OK'));
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
|
expect(tester.getSize(findBorderPainter().first), const Size(96.0, 70.0));
|
||||||
});
|
});
|
||||||
|
@ -1457,4 +1457,44 @@ void main() {
|
|||||||
|
|
||||||
expect(focusNode2.hasFocus, isTrue);
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user