I have an amount input text field with custom TextEditingController that has symbol e.i. 200 $, when I try to select the whole text |2 000 $| (| - selection pins), an exception is thrown. I have tried to do it in the level of input formatter, but my symbol has different text style
════════ Exception caught by gesture ═══════════════════════════════════════════
Range end 12 is out of text of length 7
'package:flutter/src/services/text_input.dart':
Failed assertion: line 968 pos 12: 'range.end >= 0 && range.end <= text.length'
════════════════════════════════════════════════════════════════════════════════
[AppMetricaPlugin.MainFacade] error caught by handler Range end 10 is out of text of length 7
[AppMetricaPlugin.MainFacade] _AssertionError ('package:flutter/src/services/text_input.dart': Failed assertion: line 968 pos 12: 'range.end >= 0 && range.end <= text.length': Range end 10 is out of text of length 7)
[AppMetricaPlugin.MainFacade] #0 _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
#1 _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
#2 TextEditingValue._textRangeIsValid (package:flutter/src/services/text_input.dart:968:12)
#3 TextEditingValue.toJSON (package:flutter/src/services/text_input.dart:921:12)
#4 _PlatformTextInputControl.setEditingState (package:flutter/src/services/text_input.dart:2276:13)
#5 TextInput._setEditingState (package:flutter/src/services/text_input.dart:1957:15)
#6 TextInputConnection.setEditingState (package:flutter/src/services/text_input.dart:1381:25)
#7 EditableTextState._updateRemoteEditingValueIfNeeded (package:flutter/src/widgets/editable_text.dart:3379:27)
#8 EditableTextState.endBatchEdit (package:flutter/src/widgets/editable_text.dart:3368:5)
#9 EditableTextState._formatAndSetValue (package:flutter/src/widgets/editable_text.dart:3919:5)
#10 EditableTextState.userUpdateTextEditingValue (package:flutter/src/widgets/editable_text.dart:4271:5)
#11 TextSelectionOverlay._handleSelectionHandleChanged (package:flutter/src/widgets/text_selection.dart:900:23)
#12 TextSelectionOverlay._handleSelectionEndHandleDragUpdate (package:flutter/src/widgets/text_selection.dart:776:5)
#13 SelectionOverlay._handleEndHandleDragUpdate (package:flutter/src/widgets/text_selection.dart:1212:28)
#14 DragGestureRecognizer._checkUpdate.<anonymous closure> (package:flutter/src/gestures/monodrag.dart:581:55)
#15 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:315:24)
#16 DragGestureRecognizer._checkUpdate (package:flutter/src/gestures/monodrag.dart:581:7)
#17 DragGestureRecognizer.handleEvent (package:flutter/src/gestures/monodrag.dart:422:9)
#18 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:98:12)
#19 PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:143:9)
#20 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:633:13)
#21 PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:141:18)
#22 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:127:7)
#23 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:495:19)
#24 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:475:22)
#25 RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:430:11)
#26 GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:420:7)
#27 GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:383:5)
#28 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:330:7)
#29 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:299:9)
#30 _rootRunUnary (dart:async/zone.dart:1415:13)
#31 _CustomZone.runUnary (dart:async/zone.dart:1308:19)
#32 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1217:7)
#33 _invoke1 (dart:ui/hooks.dart:330:10)
#34 PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:429:7)
#35 _dispatchPointerDataPacket (dart:ui/hooks.dart:262:31)
---------------- My custom TextEditingController
class AmountBigController extends TextEditingController {
AmountBigController({
required this.symbol,
required this.focusNode,
this.includeSymbolInHint = true,
String? hintText,
super.text = '',
}) : _hintText = hintText;
final String symbol;
final FocusNode focusNode;
final bool includeSymbolInHint;
String? _hintText;
void setHintText(String text) {
_hintText = text;
notifyListeners();
}
@override
void clear() {
value = const TextEditingValue(
text: '',
selection: TextSelection.collapsed(offset: 0),
);
}
@override
TextSpan buildTextSpan({
required BuildContext context,
required bool withComposing,
TextStyle? style,
}) {
final List<TextSpan> children = [];
final isHintShown = focusNode.hasFocus && value.text.isEmpty;
final TextStyle? textStyle =
isHintShown ? Typographies.textMediumHelper : style;
late final TextSpan symbolTextSpan;
if (isHintShown) {
if (includeSymbolInHint) {
symbolTextSpan = TextSpan(
text: symbol,
style: textStyle,
);
} else {
symbolTextSpan = TextSpan(
text: '',
style: textStyle,
);
}
} else {
symbolTextSpan = TextSpan(
text: symbol,
style: textStyle,
);
}
if (text.isEmpty) {
if (focusNode.hasFocus) {
children.insert(
0,
TextSpan(text: _hintText, style: Typographies.textMediumHelper),
);
} else {
children.insert(
0,
TextSpan(
text: '0',
style: style,
),
);
}
} else {
children.add(TextSpan(style: style, text: text));
}
children.insert(children.length, symbolTextSpan);
return TextSpan(children: children);
}
}
You are getting the error because the text
value in AmountBigController
is not in sync with the result of buildTextSpan
.
Your AmountBigController
has a TextEditingValue
property, which describes the current text value and selection value of the TextField. (The selection value also includes the current cursor position). For example, when you call the text
getter in this line:
children.add(TextSpan(style: style, text: text)
you are getting the current text saved in TextEditingValue
The actual displaying of the text is managed by the method buildTextSpan
. You have overridden this method and returned a new TextSpan
. In your TextSpan, the length of the text is increased by one -- you add a $ symbol to the end.
So the text displays as '2000$' , but the internal text value has not been updated, so is actually '2000'. When you select the text, the AmountBigController
tries to update the selection property to something like: TextRange(start: 0, end: 5)
. Now the TextEditingValue
has a text
value of '2000' and a selection
value that stretches over 5 characters.
So now you get a range error because you cannot have a TextSelection
that is longer than the length of the text.
It is fine to set different styles in the TextField by updating buildTextSpan
. However, you need to make sure the TextEditingValue
is updated to reflect this. So first, create a TextInputFormatter
like this one:
class SymbolHintFormatter extends TextInputFormatter {
final String symbol;
SymbolHintFormatter({required this.symbol});
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
String newText = newValue.text;
if (newValue.text.isEmpty) {
newText = symbol;
} else {
newText = newText.replaceAll(symbol, '');
newText = '$symbol$newText';
}
return newValue.copyWith(text: newText);
}
}
Using this SymbolHintFormatter
means the symbol will be in the TextEditingController
s text
property.
Add this to your TextField
:
TextField(
inputFormatters:
symbol != null ? [SymbolHintFormatter(symbol: symbol!)] : [],
controller: AmountBigController(
symbol: symbol ?? '',
focusNode: focusNode,
includeSymbolInHint: symbol != null,
),
),
Now you can change buildTextSpan
. Adding the symbol as a separate TextSpan
child will now be fine, since the TextEditingValue
also has the symbol in its text
property:
@override
TextSpan buildTextSpan({
required BuildContext context,
TextStyle? style,
required bool withComposing,
}) {
final List<TextSpan> children = [];
final isHintShown = focusNode.hasFocus && value.text == symbol;
if (includeSymbolInHint && isHintShown) {
final textStyle = isHintShown ? Typographies.textMediumHelper : style;
children.add(TextSpan(text: symbol, style: textStyle));
children.add(TextSpan(text: text..replaceAll(symbol, '')));
return TextSpan(children: children);
} else {
return TextSpan(text: text, style: style);
}
}