简体   繁体   中英

Material Textfield text is displayed backwards but CupertinoTextfield text is displayed correctly Flutter

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));

In use:

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));
                                    },
                                  ),
                      ),

update 8 aug 2020:

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 doctor on Catalina:

[✓] 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!

Update 4 Feb 2021

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM