158

I want to add ,00 at the end of the value of the textfield.

It works well in iOS device, but in android cursor moves to starting point of the value in textfield. As a result, I'm unable to add ,00.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Parth Bhanderi
  • 2,378
  • 3
  • 15
  • 30

22 Answers22

370

I've been having the same problem with setting a TextEditingController and this is what worked for me.

controller.text = someString;
controller.selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length));

TextSelection.fromPosition() does the following (from the documentation):

Creates a collapsed selection at the given text position. A collapsed selection starts and ends at the same offset, which means it contains zero characters but instead serves as an insertion point in the text.

Edit - another version, which is a bit shorter and suggested by others here:

controller.text = someString;
controller.selection =
          TextSelection.collapsed(offset: controller.text.length);
jwehrle
  • 4,414
  • 1
  • 17
  • 13
  • 3
    You saved me !! many thanks. I actually only need to do this with `Material Textfield` , `CupertinoTextfield` behaves as expected.. when assign a manipulated text to its `TextEditingController` cursor is always at the end. As I thing that this should be the default behavior for a textfield I filed an Issue to the Flutter team as this kinda kills Flutter purpose if you have different behaviors like this.. it ain't really multi platform.. I'm sure that this is due to different teams.. but hey, the should agree on things.. Anyways.. thanks again for the elegant workaround. – Vincenzo Jul 05 '20 at 13:13
  • Hi. After I upgraded to Catalina, Android Studio 4.0 Flutter latest dev channel the code from your solution is not working anymore. Do you know about any changes that invalidate this solution? Here you can find the code I'm using in my app https://stackoverflow.com/questions/62244027/material-textfield-text-is-displayed-backwards-but-cupertinotextfield-text-is-di. I actually found out that this solution is only needed on web.. on device ( only tested on iPad ) new text gets just added at the end..as expected. – Vincenzo Aug 08 '20 at 00:26
  • I forgot.. here is a sample app I put on GitHub to show the problem to the flutter team https://github.com/vinnytwice/material_textfield_issue_test ;) Cheers. – Vincenzo Aug 08 '20 at 00:42
  • 1
    ok.. checked on iPad and your code is also needed on iOS after upgrading to Catalina/Android Studio 4.0, Xcode 11.5.. well.. at least now all platforms are aligned .. lol – Vincenzo Aug 08 '20 at 09:13
  • 2
    This doesn't work when keyboard has dictionary, autocorrect and/or suggestions – Jose Georges Jan 13 '21 at 14:44
  • That's interesting, can you provide a code sample? I'm using this solution with overlay popup suggestions and I'm wondering how your situation is different. – jwehrle Jan 13 '21 at 19:42
30

This worked for me:

_inputCtrl.value = TextEditingValue(
  text: suggestion,
  selection: TextSelection.collapsed(offset: suggestion.length),
);

https://github.com/flutter/flutter/issues/11416#issuecomment-507040665

William
  • 884
  • 2
  • 12
  • 23
  • Only this solution worked for me. The other ones had timing issues because setting the text with editingController.text = 'something'; took some milliseconds and therefore the subsequent editingController.selection = TextSelection.fromPosition(...); wouldn't have any effect. –  Nov 25 '20 at 16:47
  • That's the only solution worked for me. I guess there's a sync issue with the previous answers, giving the text and the selection at the same like this works like a charm. – mirkancal Dec 11 '20 at 09:38
  • This should be the accepted answer tbh – Lucas Goldner Aug 12 '22 at 14:15
21

I solved my issue by using this

this will set your cursor at the end in the textfield.

you can call the code from wherever and whenever it will set the cursor location when it is called.

or simple place this code in onChanged() method of textfield

final val = TextSelection.collapsed(offset: _textTEC.text.length); 
 _textTEC.selection = val;
Vicky Salunkhe
  • 9,869
  • 6
  • 42
  • 59
17

You can use .. operator If you are setting value in the controller as below:

final _controller = TextEditingController()..text = txtValue
      ..selection = TextSelection.collapsed(offset: txtValue.length);

)

I think it's very useful when you are setting value runtime.

Pratik Butani
  • 60,504
  • 58
  • 273
  • 437
12

Simple and easy solution to move cursor at the end of position after updating the text in textfield just add below line.

textController.selection =
      TextSelection.collapsed(offset: textController.text.length);
Aditya Patil
  • 1,287
  • 12
  • 19
9

This works for me like a charm.

myController.text = newText;
myController.selection = TextSelection(
    baseOffset: newText.length,
    extentOffset: newText.length)
Roberto Juárez
  • 115
  • 1
  • 4
6

The solution that seems to work is the following:

  1. Give a TextEditingController argument to the constructor of your text field:

    var myTextEditingController = TextEditingController();
    var myTextField = TextField(..., controller: myTextEditingController);
    
  2. When setting the new text inside your text field, use the TextEditingController like that:

    myTextEditingController.value.copyWith(
      text: newText,
      selection: TextSelection(
        baseOffset: newText.length,
        extentOffset: newText.length
      )
    )
    

This seems extraordinary complicated for what it achieves but it seems to be the only solution to the problem of the cursor not being updated in Flutter text fields.

John Smith Optional
  • 22,259
  • 12
  • 43
  • 61
5

Checked the notes of the setter of TextEditingController's text, it says the [text] and the [selection] shouldn't be changed at the same time, however for the phone inputting, we'd like to apply some format for the input (e.g. '131 1111 1111') while keeping the cursor always at the end .

So I just use a customized TextEditingController, it works!

class PhoneEditingController extends TextEditingController {

  @override
  set text(String newText) {
    value = value.copyWith(
    text: newText,
    selection: TextSelection.collapsed(offset: newText.length),
    composing: TextRange.empty,
  );

}
rayworks
  • 741
  • 9
  • 15
  • Maybe they've updated it? I read it as encouraging to update both at the same time instead of separately /// This property can be set from a listener added to this [TextEditingController]; however, one should **not** also set [selection] in a **separate** statement. To change both the [text] and the [selection] change the controller's [value]. – Can Rau Apr 11 '20 at 17:36
  • Pal I would give you two likes if stack overflow let me. In my case, using a phone input formatter (that is, one that adds hyphens, parenthesis, etc. when writing) it is your answer that works. Thanks. – Iván Yoed Sep 06 '21 at 21:29
3

In case your new value is too long. You should scroll the view to the new cursor position.

  1. Add TextEditingController and ScrollController to TextField.

Remember to init/dispose them in initState and dispose

TextField(
controller: controller,
scrollController: scrollController,
)
  1. Set a new value and cursor position for the TextEditingController
controller.text = newValue;
controller.selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length));
  1. Scroll the view to the position
Future.delayed(Duration(milliseconds: 50),(){
 scrollController.jumpTo(scrollCtrl.position.maxScrollExtent);
});

ANDYNVT
  • 531
  • 4
  • 19
2

the following code worked perfectly for me.

_controller.value = _controller.value.copyWith(
  text: newText,
  selection: TextSelection.fromPosition(
    TextPosition(offset: newText.length),
  ),
);
mrdev
  • 617
  • 7
  • 8
2

Using _textController.text.length as the cursor's base offset doesn't work if you want to add your special characters (in your case 00) somewhere in the middle of the text. In such cases, the following solution should work:

String specialChars = '00';
int length = specialChars.length;
// current cursor position, where you want to add your special characters
int cursorPos = _textController.selection.base.offset;
// the text before the point you want to add the special characters
String prefixText = _textController.text.substring(0, cursorPos);
// the text after the point ...
String suffixText = _textController.text.substring(cursorPos);

_textController.text = prefixText + specialChars + suffixText;
// set the cursor position right after the special characters
_textController.selection = TextSelection(baseOffset: cursorPos + length, extentOffset: cursorPos + length);

or you can do it like this, it's the same thing:

int cursorPos = _textController.selection.base.offset;
_textController.value = _textController.value.copyWith(
  text: _textController.text.replaceRange(cursorPos, cursorPos, '00'),
  selection: TextSelection.fromPosition(TextPosition(offset: cursorPos + 2))
);
hedisam
  • 521
  • 6
  • 10
2

Create a new Dart class, extending TextEditingController and you can use your custom TextController instead that behaves:

class TextController extends TextEditingController {

  TextController({String text}) {
    this.text = text;
  }

  set text(String newText) {
    value = value.copyWith(
      text: newText,
      selection: TextSelection.collapsed(offset: newText.length),
      composing: TextRange.empty
    );
  }
}
2

There are a lot of solutions but in the code below I've merged the best answers from Github and SO.

This below has null-safety for Dart >= 2.12 and uses latest TextSelection APIs

import 'package:flutter/widgets.dart';

class TextEditingControllerWithEndCursor extends TextEditingController {

  TextEditingControllerWithEndCursor({
    String? text
  }): super(text: text);

  @override
  set text(String newText) {
    value = value.copyWith(
      text: newText,
      selection: TextSelection(
        baseOffset: newText.length,
        extentOffset: newText.length
      ),
      composing: TextRange.empty,
    );
  }
}
GraSim
  • 3,830
  • 1
  • 29
  • 35
MatPag
  • 41,742
  • 14
  • 105
  • 114
1

I have a TextField, and I have a method that appends text to it periodically. I wanted to scroll horizontally to the rightmost end of that text field to see the last appended string, or I want to move the cursor to the last character appended.

The only trick that worked for me was to use a scrollController, and call the jump() method every time I append text:

TextField(
    scrollController: scrollController,
    controller: textFieldController
)

Now jump to the end of the TextField:

scrollController.jumpTo( scrollController.position.pixels+500);
skomisa
  • 16,436
  • 7
  • 61
  • 102
Mahmoud Aly
  • 578
  • 3
  • 10
1

Use a StatefulWidget and store a TextEditingController.

class SampleTextFieldextends StatefulWidget {
  const SampleTextField({super.key});

  @override
  State<SampleTextField> createState() => _SampleTextFieldState();
}

class _SampleTextFieldStateextends State<SampleTextField> {
  late final TextEditingController controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
      onChanged: (value) {
        controller.text = value;
        controller.selection =
            TextSelection.collapsed(offset: controller.text.length);
      },

      onTap: () {
        controller.selection =
            TextSelection.collapsed(offset: controller.text.length);
      },

      onTapOutside: (event) {
        FocusScope.of(context).unfocus();
      },

    );
  }
}

This answer put curser at the end of the text field in right to left (ltr) languages like Farsi / Persian / Arabic.

zex_rectooor
  • 692
  • 7
  • 26
0

You need a FocusNode and set TextSelection to place the cursor.

The code here may be a start: Position cursor at end of TextField when focused?

Gazihan Alankus
  • 11,256
  • 7
  • 46
  • 57
0

According to the documentation the possible way to set the text and position together is using value... like this:

_controller.value = _controller.value.copyWith(text: newText, selection: newSelection)
Paloma Bispo
  • 312
  • 3
  • 9
0

I also had a similar problem trying to change the text in the TextEditingController.text into a formatted string matching NumberFormat("###,###,###.##") inside onChanged while trying to preserve the current cursor location and ended up doing something like this:

TextFormField(
  controller: controller,

  onChanged: (value) {
    final formatter = NumberFormat("###,###,###.##");
    final amount = formatter.parse(value);

    //allow user to enter any letter at the end without clearing it.
    //otherwise if the user enters "." it would get truncated by
    //the formatter
    if (amount == 0 || value.startsWith(formatter.format(amount.floor()))) {
      return;
    }

    final editor = controller.value;

    //only do something if the IME is not in the middle of composing
    if (editor.composing.isCollapsed) {
      //offset is the position of the cursor
      int offset = editor.selection.extentOffset;

      final pretty = formatter.format(amount);

      if(offset >= value.length) {
        //set the offset to the length of the new string
        offset = pretty.length;

      } else {
        //count how many chars are in the string to the left
        //of the cursor position, excluding formatting chars [,.-]

        final partial = value.substring(0, offset);
        for (var unit in partial.codeUnits) {
          if (unit >= 44 && unit <= 46) offset--;
        }

        //traverse the formatted string by the same number of
        //characters skipping over the formatting chars [,.-].
        int i = 0;
        for (var unit in pretty.codeUnits) {
          if (i++ >= offset) break;
          if (unit >= 44 && unit <= 46) offset++;
        }
        //offset is now where the new cursor position should be
      }

      //finally update the controller to the new offset
      controller.value = editor.copyWith(
        text: pretty,
        selection: TextSelection.collapsed(offset: offset),
        composing: TextRange.collapsed(offset),
      );
    }
  },
)
Kernel James
  • 3,752
  • 25
  • 32
0

A simple example will help you.

 if (int.parse(mintues) > 59) mintuesController.text = '59';
                mintuesController.selection = TextSelection.fromPosition(
                    TextPosition(offset: mintues.length));
Hammad Pervez
  • 316
  • 4
  • 12
  • 1
    There are **16 existing answers** to this question, including a top-voted, accepted answer with over **two hundred votes**. Are you _certain_ your solution hasn't already been given? If not, why do you believe your approach improves upon the existing proposals, which have been validated by the community? Offering an explanation is _always_ useful on Stack Overflow, but it's _especially_ important where the question has been resolved to the satisfaction of both the OP and the community. Help readers out by explaining what your answer does different and when it might be preferred. – Jeremy Caney Jan 27 '22 at 01:02
0

The easy way is

TextField(
    decoration: InputDecoration(labelText: "Email"),
    controller: TextEditingController.fromValue(TextEditingValue(text: 
    useremail,selection: TextSelection.collapsed(offset: useremail.length))),
    onChanged: (value) => useremail= value,
)
0

in my case and because I'm using getx as state management, I solved the problem by:

TextField(
 controller: TextEditingController.fromValue(TextEditingValue(
                  text: con.nominator1String.value,
                  selection: TextSelection.collapsed(
                      offset: con.nominator1String.value.length)))
)

con is my getx controller

Ahmed.Net
  • 158
  • 2
  • 6
0

To change text cursor after change not just end of the string but also middle of string you can use this snippet on onChange property of TextField:

(newValue){
   int changedPosition =
            determineChangedPosition(previousTextFieldValue, newValue);

   // Set the cursor position at the changed position
    controller.selection = TextSelection.fromPosition(
       TextPosition(offset: newValue.length == changedPosition
                  ? changedPosition
                  : changedPosition + 1)),
    );
}

use StatefullWidget to store previousTextFieldValue and here is determineChangedPosition method:

  int determineChangedPosition(String oldValue, String newValue) {
    final minLength =
        oldValue.length < newValue.length ? oldValue.length : newValue.length;

    for (var i = 0; i < minLength; i++) {
      if (oldValue[i] != newValue[i]) {
        return i;
      }
    }
    return minLength;
  }

zex_rectooor
  • 692
  • 7
  • 26