-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Steps to reproduce
- Write a custom SpellCheckService, which in some cases returns genuin TextRanges, and in other cases, returns null
- Apply this new SpellCheckService to a TextField
- Fill the TextField with words, such that some of them are highlighted as incorrectly spelled
- From the end of the TextField, hold down backspace to repeatedly remove characters until they are all gone
Expected results
All characters should be removed one by one, while spell check runs for each change, without error.
The SpellCheckService 'fetchSpellCheckSuggestions' method definition allows for the returning of null, and the documentation does not state when it can and cannot be null.
It appears that null is intended to represent the fact that spell checking could not run (i.e, language not supported) - let's call that State 1, and an empty result set ([]) is intended to represent the spellcheck successfully running, but no errors found in the text - State 2.
The subsystem does not like the SpellCheckService transitioning from State 2 to State 1 for a singular EditableText instance.
EditableText should be more resilient to this.
Actual results
At some point, an error is generated by the spellcheck infrastrcuture, putting it into a bad state, then errors are repeatedly generated while attempting to render the text caret (caret will no longer render).
Code sample
Sample SpellCheckService which will exhibits the issue.
import 'dart:async';
import 'dart:math';
import 'dart:ui';
import 'package:flutter/services.dart';
class TestCaseSpellCheckService implements SpellCheckService {
@override
Future<List<SuggestionSpan>?> fetchSpellCheckSuggestions(Locale locale, String text) async {
// generate a boolean that is true 50% of the time
final bool shouldFail = Random().nextDouble() < 0.50;
if (shouldFail) {
return null;
}
return _processSpellCheck(text);
}
Future<List<SuggestionSpan>?> _processSpellCheck(String text) async
{
// make every word an error.
// 1. use regex to split the text into words
// 2. for each word, create a SuggestionSpan with the word as the text and a list of suggestions
// 3. return the list of SuggestionSpan, which is comprised of a TextRange and a list of suggestions
final re = RegExp(r'(\w+)');
final words = re.allMatches(text);
final suggestions = words.map((word) => SuggestionSpan(
TextRange(start: word.start, end: word.end),
['suggestion1', 'suggestion2']
)).toList();
return suggestions;
}
}Screenshots or Video
Video of repeated backspace causing error in TextField (caret then disappears as bad state prevents it from rendering)
Screen.Recording.2024-07-25.at.10.18.39.AM.mov
Screenshot of moment error occurs

Logs
Stack trace
flutter: [E] TIME: 2024-07-25T10:19:48.809862 Unhandled Exception: RangeError (start): Invalid value: Not in inclusive range 0..176: 177 ERROR: RangeError (start): Invalid value: Not in inclusive range 0..176: 177 STACKTRACE: #0 RangeError.checkValidRange (dart:core/errors.dart:360:7)
#1 _StringBase.substring (dart:core-patch/string_patch.dart:418:27)
#2 _correctSpellCheckResults (package:flutter/src/widgets/spell_check.dart:136:36)
#3 buildTextSpanWithSpellCheckSuggestions (package:flutter/src/widgets/spell_check.dart:202:30)
#4 EditableTextState.buildTextSpan (package:flutter/src/widgets/editable_text.dart:5360:14)
#5 EditableTextState.build.<anonymous closure> (package:flutter/src/widgets/editable_text.dart:5255:45)
#6 ScrollableState.build (package:flutter/src/widgets/scrollable.dart:981:44)
#7 StatefulElement.build (package:flutter/src/widgets/framework.dart:5599:27)
#8 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5487:15)
#9 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#10 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#11 StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#12 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#13 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#14 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#15 ProxyElement.update (package:flutter/src/widgets/framework.dart:5816:5)
#16 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#17 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#18 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#19 ProxyElement.update (package:flutter/src/widgets/framework.dart:5816:5)
#20 _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:105:11)
#21 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#22 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#23 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#24 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#25 StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#26 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#27 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#28 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#29 ProxyElement.update (package:flutter/src/widgets/framework.dart:5816:5)
#30 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#31 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#32 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#33 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#34 StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#35 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#36 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#37 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#38 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#39 StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#40 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#41 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#42 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#43 ProxyElement.update (package:flutter/src/widgets/framework.dart:5816:5)
#44 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#45 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#46 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#47 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#48 StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#49 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#50 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#51 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#52 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#53 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#54 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#55 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#56 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#57 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#58 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#59 StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#60 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#61 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#62 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#63 ProxyElement.update (package:flutter/src/widgets/framework.dart:5816:5)
#64 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#65 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#66 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#67 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#68 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#69 SlottedRenderObjectElement._updateChildren (package:flutter/src/widgets/slotted_render_object_widget.dart:295:33)
#70 SlottedRenderObjectElement.update (package:flutter/src/widgets/slotted_render_object_widget.dart:256:5)
#71 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#72 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#73 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#74 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#75 StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#76 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#77 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#78 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#79 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#80 StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#81 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#82 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#83 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#84 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#85 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#86 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#87 StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#88 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#89 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#90 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#91 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#92 StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#93 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#94 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#95 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#96 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#97 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#98 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#99 StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#100 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#101 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#102 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#103 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#104 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#105 SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
#106 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#107 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#108 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#109 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#110 StatefulElement.update (package:flutter/src/widgets/framework.dart:5673:5)
#111 Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#112 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#113 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#114 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#115 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2905:19)
#116 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1136:21)
#117 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:443:5)
#118 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1392:15)
#119 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1313:9)
#120 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1171:5)
#121 _invoke (dart:ui/hooks.dart:312:13)
#122 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:419:5)
#123 _drawFrame (dart:ui/hooks.dart:283:31)
flutter: [E] TIME: 2024-07-25T10:19:48.845998 Unhandled Exception: Bad state: TextPainter.text must be set to a non-null value before using the TextPainter. ERROR: Bad state: TextPainter.text must be set to a non-null value before using the TextPainter. STACKTRACE: #0 TextPainter.layout (package:flutter/src/painting/text_painter.dart:1168:7)
#1 RenderEditable._computeTextMetricsIfNeeded (package:flutter/src/rendering/editable.dart:2256:18)
#2 RenderEditable.getLocalRectForCaret (package:flutter/src/rendering/editable.dart:1788:5)
#3 EditableTextState._updateComposingRectIfNeeded (package:flutter/src/widgets/editable_text.dart:4487:38)
#4 EditableTextState._schedulePeriodicPostFrameCallbacks (package:flutter/src/widgets/editable_text.dart:4393:5)
#5 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1392:15)
#6 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1326:11)
#7 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1171:5)
#8 _invoke (dart:ui/hooks.dart:312:13)
#9 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:419:5)
#10 _drawFrame (dart:ui/hooks.dart:283:31)
Flutter Doctor output
Issue produces on both Windows and MacOS. Not tested on mobile, but expect behavior to be the same.
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.22.3, on macOS 14.0 23A344 darwin-x64, locale en-NZ)
[✓] Android toolchain - develop for Android devices (Android SDK version 32.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.3)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.2)
[✓] VS Code (version 1.91.1)
[✓] Connected device (3 available)
[✓] Network resources
• No issues found!