Closed
Description
Steps to reproduce
- Enable screenreader
- Navigate to TextField and type value its announce the typing text but its not populate the value on textfield
Expected results
It should allow to type the text value
Actual results
Text input field does not work very well with screen reader. If screen reader is hovering, it sometimes says the user is typing but text is not actually coming up. This should be fixed so that, if a user is typing and the screen reader is reading it, the text should come up.
Code sample
Code sample
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/semantics.dart';
void main() {
runApp(const ScreenReaderIssueApp());
}
class ScreenReaderIssueApp extends StatelessWidget {
const ScreenReaderIssueApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Screen Reader Issue Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const ScreenReaderIssuePage(),
);
}
}
class ScreenReaderIssuePage extends StatefulWidget {
const ScreenReaderIssuePage({super.key});
@override
State<ScreenReaderIssuePage> createState() => _ScreenReaderIssuePageState();
}
class _ScreenReaderIssuePageState extends State<ScreenReaderIssuePage> {
final TextEditingController _textController = TextEditingController();
final FocusNode _focusNode = FocusNode();
bool _isTyping = false;
String _lastAnnounced = '';
@override
void initState() {
super.initState();
_textController.addListener(_onTextChanged);
_focusNode.addListener(_onFocusChanged);
}
@override
void dispose() {
_textController.removeListener(_onTextChanged);
_focusNode.removeListener(_onFocusChanged);
_textController.dispose();
_focusNode.dispose();
super.dispose();
}
void _onFocusChanged() {
setState(() {
// Update UI based on focus changes
if (_focusNode.hasFocus) {
_isTyping = true;
} else {
_isTyping = false;
}
});
}
void _onTextChanged() {
// This demonstrates the issue - sometimes the screen reader announces text
// changes but the text doesn't actually update in the field
if (_textController.text != _lastAnnounced && _focusNode.hasFocus) {
_lastAnnounced = _textController.text;
// Log for debugging
debugPrint('Text changed to: ${_textController.text}');
// In the actual issue, sometimes this text would be announced by the screen reader
// but wouldn't appear in the field
SchedulerBinding.instance.addPostFrameCallback((_) {
// Simulate the issue where screen reader announces but text doesn't appear
if (_isTyping) {
SemanticsService.announce(
'Typing: ${_textController.text}',
TextDirection.ltr,
);
}
});
}
}
void _sendMessage() {
if (_textController.text.trim().isNotEmpty) {
setState(() {
// In a real app, this would send the message
debugPrint('Message sent: ${_textController.text}');
_textController.clear();
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Screen Reader Issue Demo'),
),
body: Column(
children: [
Expanded(
child: Center(
child: Text(
'This demo reproduces the screen reader issue where the screen reader '
'announces typing but text doesn\'t appear in the field.\n\n'
'Current text: "${_textController.text}"',
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16),
),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: AccessibleSearchBar(
controller: _textController,
focusNode: _focusNode,
onSubmit: (_) => _sendMessage(),
isTyping: _isTyping,
),
),
],
),
);
}
}
class AccessibleSearchBar extends StatefulWidget {
final TextEditingController controller;
final FocusNode focusNode;
final Function(String) onSubmit;
final bool isTyping;
const AccessibleSearchBar({
super.key,
required this.controller,
required this.focusNode,
required this.onSubmit,
required this.isTyping,
});
@override
State<AccessibleSearchBar> createState() => _AccessibleSearchBarState();
}
class _AccessibleSearchBarState extends State<AccessibleSearchBar> {
int _calculateLines(String text, double maxWidth) {
if (text.isEmpty) return 1;
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: const TextStyle(fontSize: 16)),
textDirection: TextDirection.ltr,
maxLines: 5,
)..layout(maxWidth: maxWidth);
return textPainter.computeLineMetrics().length;
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final lines =
_calculateLines(widget.controller.text, constraints.maxWidth - 80);
final currentLines = lines.clamp(1, 5);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
decoration: BoxDecoration(
color: Colors.grey[200],
border: Border.all(color: Colors.grey[400]!),
borderRadius: BorderRadius.circular(8.0),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: Semantics(
textField: true,
// This is part of the reproduction of the issue
// The semantic properties don't always match the actual text field
label: 'Type your message here',
value: widget.controller.text,
hint: 'Type your message here',
onDidGainAccessibilityFocus: () {
// This helps demonstrate the issue where screen reader focus
// doesn't correctly sync with the input field
widget.focusNode.requestFocus();
},
child: TextFormField(
controller: widget.controller,
focusNode: widget.focusNode,
maxLines: currentLines,
minLines: 1,
style: const TextStyle(fontSize: 16),
decoration: const InputDecoration(
border: InputBorder.none,
hintText: 'Type your message',
contentPadding: EdgeInsets.symmetric(vertical: 12.0),
),
onFieldSubmitted: widget.onSubmit,
),
),
),
if (widget.controller.text.trim().isNotEmpty)
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: ElevatedButton(
onPressed: () => widget.onSubmit(widget.controller.text),
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
padding: const EdgeInsets.all(12),
),
child: const Icon(Icons.send),
),
),
],
),
);
},
);
}
}
Screenshots or Video

Screenshots / Video demonstration
[Upload media here]
Logs
Logs
[Paste your logs here]
Flutter Doctor output
Doctor output
flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.24.4, on macOS 15.5 24F74 darwin-arm64, locale
en-IN)
[✗] Android toolchain - develop for Android devices
✗ Unable to locate Android SDK.
Install Android Studio from:
https://developer.android.com/studio/index.html
On first launch it will assist you in installing the Android SDK
components.
(or visit https://flutter.dev/to/macos-android-setup for detailed
instructions).
If the Android SDK has been installed to a custom location, please use
`flutter config --android-sdk` to update to that location.
[!] Xcode - develop for iOS and macOS (Xcode 16.2)
✗ CocoaPods not installed.
CocoaPods is a package manager for iOS or macOS platform code.
Without CocoaPods, plugins will not work on iOS or macOS.
For more info, see https://flutter.dev/to/platform-plugins
For installation instructions, see
https://guides.cocoapods.org/using/getting-started.html#installation
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.2)
[✓] VS Code (version 1.100.2)
[✓] Connected device (3 available)
[✓] Network resources
! Doctor found issues in 2 categories.