Fix: Announce only the first error message for better accessibility (#156399)

## Issue
This pull request addresses an accessibility issue where all form error
messages were concatenated and announced simultaneously, overwhelming
users relying on screen readers like VoiceOver and TalkBack.
This is the issue link: https://github.com/flutter/flutter/issues/156340

## Update
The change ensures that only the first error message is announced,
providing a more manageable and user-friendly experience.

## 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.
- [x] 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.

---------

Co-authored-by: chunhtai <47866232+chunhtai@users.noreply.github.com>
This commit is contained in:
Aziz Chebbi 2024-11-28 02:04:08 +01:00 committed by GitHub
parent fdc666158e
commit d541354936
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 42 additions and 31 deletions

View File

@ -357,7 +357,10 @@ class FormState extends State<Form> {
if (!validateOnFocusChange || !field._focusNode.hasFocus) {
final bool isFieldValid = field.validate();
hasError = !isFieldValid || hasError;
errorMessage += field.errorText ?? '';
// Ensure that only the first error message gets announced to the user.
if (errorMessage.isEmpty) {
errorMessage = field.errorText ?? '';
}
if (invalidFields != null && !isFieldValid) {
invalidFields.add(field);
}

View File

@ -139,7 +139,7 @@ void main() {
await checkErrorText('');
});
testWidgets('Should announce error text when validate returns error', (WidgetTester tester) async {
testWidgets('Should announce only the first error message when validate returns errors', (WidgetTester tester) async {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
await tester.pumpWidget(
MaterialApp(
@ -151,8 +151,15 @@ void main() {
child: Material(
child: Form(
key: formKey,
child: TextFormField(
validator: (_)=> 'error',
child: Column(
children: <Widget>[
TextFormField(
validator: (_) => 'First error message',
),
TextFormField(
validator: (_) => 'Second error message',
),
],
),
),
),
@ -162,21 +169,22 @@ void main() {
),
);
formKey.currentState!.reset();
await tester.enterText(find.byType(TextFormField), '');
await tester.enterText(find.byType(TextFormField).first, '');
await tester.pump();
// Manually validate.
expect(find.text('error'), findsNothing);
expect(find.text('First error message'), findsNothing);
expect(find.text('Second error message'), findsNothing);
formKey.currentState!.validate();
await tester.pump();
expect(find.text('error'), findsOneWidget);
expect(find.text('First error message'), findsOneWidget);
expect(find.text('Second error message'), findsOneWidget);
final CapturedAccessibilityAnnouncement announcement = tester.takeAnnouncements().single;
expect(announcement.message, 'error');
expect(announcement.message, 'First error message');
expect(announcement.textDirection, TextDirection.ltr);
expect(announcement.assertiveness, Assertiveness.assertive);
});
});
testWidgets('isValid returns true when a field is valid', (WidgetTester tester) async {
final GlobalKey<FormFieldState<String>> fieldKey1 = GlobalKey<FormFieldState<String>>();