I have a textfield connected to its TextEditingController()
. Inside the onChanged:
callback I perform a text check to only allow for time input. When running on iOS CupertinoTextfield
is used and it behaves as expected, at every input cursor moves so next digit is at the right position, so inputting 1000 will result in 10:00. When running on web or Android Material Textfield
is used instead, the problem is that text is displayed backward as the cursor stays at first position so inputting 1000 will result in 00:01.. I tried enabling autofocus: true
, but doesn't help. I tried with textDirection: TextDirection.ltr,
but didn't fixit either. I also tried a solution from another post here grabbing the controller selection and reapplying it on the checked text but it didn't help either. What am I missing to set for Material Textfield? As always thank you very much for your time and help.
This is the widget:
Expanded(
flex: 2,
child: kIsWeb
? TextField(
keyboardType: TextInputType.numberWithOptions(),
textDirection: TextDirection.ltr,
autofocus: true,
controller: monMorOp,
onChanged: (value) {
TextSelection previousSelection =
monMorOp.selection;
monMorOp.text = validateTimeFormat(value);
monMorOp.selection = previousSelection;
},
)
: Platform.isIOS
? CupertinoTextField(
keyboardType:
TextInputType.numberWithOptions(),
controller: monMorOp,
onChanged: (value) {
monMorOp.text = validateTimeFormat(value);
},
)
: TextField(
keyboardType:
TextInputType.numberWithOptions(),
controller: monMorOp,
onChanged: (value) {
monMorOp.text = validateTimeFormat(value);
},
),
),
an this is the text checking method:
String validateTimeFormat(String value) {
print('call back method called');
// String numb = event.text;
print('input text is $value');
String cleanNumb = value.replaceAll(RegExp(':'), '').substring(0);
print('cleaned input text is $cleanNumb');
RegExp isDigit = RegExp(r'^[\d]{1,4}$'); // is digit 1 to 4 characters
// RegExp isDigit = RegExp(r'^[\d]$'); // is digit
RegExp input;
String text;
int lenght;
String replaced;
if (isDigit.hasMatch(cleanNumb)) {
print('text is 1-4 digits');
text = cleanNumb;
lenght = text.length;
// print('lenght is $lenght');
if (lenght == 1) {
// first digit
//allow 0-2
input = RegExp(r'^[0-2]$');
input.hasMatch(text[0])
? print('text is : $text')
: print('text is: not valid');
return input.hasMatch(text[lenght - 1]) ? text : '';
} else if (lenght == 2) {
// second digit
int first = int.parse(text[0]);
print('firstDigit is $first');
if (first == 008 || first == 1) {
// allow 0-9
input = RegExp(r'^[0-9]$');
input.hasMatch(text[lenght - 1])
? print('text is : $text')
: print('text is : ${text.substring(0, lenght - 1)}');
return input.hasMatch(text[lenght - 1])
? text
: text.substring(0, lenght - 1);
} else {
// allow 0-3
input = RegExp(r'^[0-3]$');
input.hasMatch(text[lenght - 1])
? print('text is : $text')
: print('text is : ${text.substring(0, lenght - 1)}');
return input.hasMatch(text[lenght - 1])
? text
: text.substring(0, lenght - 1);
}
}
if (lenght == 3) {
//third digit
// add : at lenght-1
// allow 0-5
input = RegExp(r'^[0-5]$');
input.hasMatch(text[lenght - 1])
? replaced = text.replaceRange(2, lenght, ':${text.substring(2)}')
: replaced = text.substring(0, lenght - 1);
print('text is : $replaced');
return replaced;
}
if (lenght == 4) {
// fourth digit
// allow 0-9
input = RegExp(r'^[0-9]$');
input.hasMatch(text[lenght - 1])
? replaced = text.replaceRange(2, lenght, ':${text.substring(2)}')
: replaced = text.substring(0, lenght - 1);
print('text is : $replaced');
return replaced;
}
} else {
// discard extra digit
print('more than 4 digits');
lenght = cleanNumb.length;
replaced =
cleanNumb.replaceRange(2, lenght, ':${cleanNumb.substring(2, 4)}');
print('text is : $replaced');
return replaced;
}
}
After almost a month I found a solution to the problem thanks to jwehrle answer here how to set cursor position at the end of the value in flutter in textfield? . I actually filed an issue to the Flutter team as Material Textfield
behaves different from CupertinoTextfield
when assigning a manipulated text to its TextEditingController
and it seems to be platform related, I only tested it on web and iPad.I think of this solution more of a workaround as this shouldn't be necessary as it isn't needed in CupertinoTextfield
on iOS.
In onChanged:
callback add this after assigning the manipulated input text to the controller:
textfieldController.selection = TextSelection.fromPosition(TextPosition(offset: textfieldController.text.length));
Expanded(
flex: 2,
child: UniversalPlatform.isWeb
? TextField(
keyboardType: TextInputType.numberWithOptions(),
textDirection: TextDirection.ltr,
autofocus: true,
controller: monMorOp,
style: TextStyle(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.w500),
onChanged: (value) {
monMorOp.text = validateTimeFormat(value);
monMorOp.selection =
TextSelection.fromPosition(TextPosition(
offset: monMorOp.text.length));
},
)
: UniversalPlatform.isIOS
? CupertinoTextField(
keyboardType:
TextInputType.numberWithOptions(),
controller: monMorOp,
style: TextStyle(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.w500),
onChanged: (value) {
monMorOp.text = validateTimeFormat(value);
},
)
: TextField(
keyboardType:
TextInputType.numberWithOptions(),
controller: monMorOp,
style: TextStyle(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.w500),
onChanged: (value) {
monMorOp.text = validateTimeFormat(value);
monMorOp.selection =
TextSelection.fromPosition(
TextPosition(
offset:
monMorOp.text.length));
},
),
),
As I upgraded to Catalina, Android Studio 4.0 the workaround is now needed also for iOS. Not sure if it's due to a change in iOS 13.5 or Flutter.. but on web it now scrambles digits in pair.. Looks like the fixed something and broken something else..
[✓] Flutter (Channel master, 1.21.0-8.0.pre.98, on Mac OS X 10.15.4 19E287, locale it-IT)
• Flutter version 1.21.0-8.0.pre.98 at /Users/vinnytwice/Developer/flutter
• Framework revision 77b4505c80 (31 hours ago), 2020-08-06 18:51:02 -0700
• Engine revision cd3ea1e839
• Dart version 2.10.0 (build 2.10.0-3.0.dev fcafd43f2c)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.1)
• Android SDK at /Users/vinnytwice/Library/Android/sdk
• Platform android-29, build-tools 30.0.1
• Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 11.5)
• Xcode at /Volumes/ProjectsSSD/Xcode.app/Contents/Developer
• Xcode 11.5, Build version 11E608c
• CocoaPods version 1.9.3
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 4.0)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin version 47.1.2
• Dart plugin version 193.7361
• Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
[✓] Connected device (3 available)
• iPad Pro (12.9-inch) (4th generation) (mobile) • 08E2351F-A170-4C2E-A8DE-8FED3B5E3124 • ios •
com.apple.CoreSimulator.SimRuntime.iOS-13-5 (simulator)
• Web Server (web) • web-server • web-javascript • Flutter
Tools
• Chrome (web) • chrome • web-javascript • Google
Chrome 84.0.4147.105
• No issues found!
Instead of assigning the new Textfield's value from the onChanged
callback to it's controller's .text
parameter after the text formatting it as
monMorOp.text = validateTimeFormat(value);
and then displacing the controller selection by offsetting it of the controller's .text.length
amount as
monMorOp.selection = TextSelection.fromPosition(TextPosition(offset: monMorOp.text.length));
I now assign the new and formatted Textfield text to the controller's .value
and offset its selection position by the Textfield's formatted value as
onChanged: (value) {
value = validateTimeFormat(value);
monMorCl.value = TextEditingValue(
text: value,
selection: TextSelection.collapsed(offset: value.length));
},
Hope it helps others as I see this is quite changing lately with any Flutter/Dart update. Cheers
class FixedOffsetTextEditingController extends TextEditingController {
@override
set text(String newText) {
value = value.copyWith(
text: newText,
selection: TextSelection.collapsed(offset: newText.length),
composing: TextRange.empty,
);
}
}
This worked for me as of 04/02/2021:)
I came here looking for a similar thing, I'm fairly sure its not directly the Material TextField's fault though, as its fine if I compile + run it on an Android emulator. It is however wrong when compiling the same code for desktop.
Did you report a bug?
Still broken for me @
Flutter 2.0.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision adc687823a (2 weeks ago) • 2021-04-16 09:40:20 -0700
Engine • revision b09f014e96
Tools • Dart 2.12.3
Aha: Its a gtk+ version issue, see: https://github.com/flutter/flutter/issues/47745 - you need at least 3.24.27 (I have 3.24.26... typical)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.