I want to format pin entry with a dash after at least 5 characters have been entered. So, each user enter the 10 digit pin code a dash is added automatically after every 5 characters.

TextField(
  maxLength: 10,
  keyboardType: TextInputType.text,
  textCapitalization: TextCapitalization.characters,
  onChanged: (text) {
    pin = text;
  },
  textAlign: TextAlign.left,
  // keyboardType: TextInputType.visiblePassword,
  decoration: InputDecoration(
    errorText: _errorText,
    icon: Icon(
      Icons.dialpad,
    ),
    labelText: '8-digit PIN',
    contentPadding: EdgeInsets.symmetric(vertical: 10.0),
    focusedBorder: UnderlineInputBorder(
      borderSide: BorderSide(color: Colors.transparent, width: 2),
    ),
    focusedErrorBorder: UnderlineInputBorder(
      borderSide: BorderSide(color: Colors.red, width: 2),
    ),
    errorBorder: UnderlineInputBorder(
      borderSide: BorderSide(color: Color(0xFFF696969), width: 1),
    ),
  ),
),


Solution 1: HTMHell

You should add a controller to your TextField, so you can control the value of the text field.

TextField(
...
  controller: _controller,
...
)

Change your onChange event function to something like this:

onChanged: (String text) {
  final sanitizedText = text.replaceAll('-', '');

  _controller.text = _addDashes(sanitizedText);

  _controller.selection = TextSelection.collapsed(offset: _controller.text.length); 
},

This takes the current text inside the TextField, removes the dashes, and stores it in a variable. Then the function replaces the text of the TextField using the controller, to the value of the sanitized text with _addDashes function applied to it. Additionally, it will move the cursor to the end of the text field, since it jumped to the beginning when we changed the value.

String _addDashes(String text) {
  const int addDashEvery = 5;
  String out = '';

  for (int i = 0; i < text.length; i++) {
    if (i + 1 > addDashEvery && i % addDashEvery == 0) {
      out += '-';
    }

    out += text[i];
  }

  return out;
}


Solution 2: Wilson Toribio

You can try this approach, use TextEditingController to controller when the text reaches an specific length and _controller.selection to continue where the dash was added, if this selection property is not handled then the selection will be restarted to the beginning:

class _MyHomePageState extends State<MyHomePage> {

  final TextEditingController _controller = TextEditingController();
  late FocusNode myFocusNode;

  @override
  void initState() {

    myFocusNode = FocusNode();
    _controller.addListener(() {

      if(_controller.text.length == 4) {
        _controller.text = _controller.text + '-';
        _controller.selection = TextSelection.fromPosition(TextPosition(offset: _controller.text.length));;
      }

    });

    super.initState();
  }

  @override
  void didChangeDependencies() {
    print(_controller.text);
    super.didChangeDependencies();
  }

  @override
  void dispose() {
    _controller.dispose();
    myFocusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.lightBlue,
      body: Container(
        child: SingleChildScrollView(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [

                TextField(
                  maxLength: 10,
                  keyboardType: TextInputType.text,
                  textCapitalization: TextCapitalization.characters,
                  controller: _controller,
                  focusNode: myFocusNode,
                  onChanged: (text) {},
                  textAlign: TextAlign.left,
                  decoration: const InputDecoration(
                    icon: Icon(
                      Icons.dialpad,
                    ),
                    labelText: '8-digit PIN',
                    contentPadding: EdgeInsets.symmetric(vertical: 10.0),
                    focusedBorder: UnderlineInputBorder(
                      borderSide:
                      BorderSide(color: Colors.transparent, width: 2),
                    ),
                    focusedErrorBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: Colors.red, width: 2),
                    ),
                    errorBorder: UnderlineInputBorder(
                      borderSide:
                      BorderSide(color: Color(0xFFF696969), width: 1),
                    ),
                  ),
                ),

              ],
            ),
          ),
        ),
      ),

    );
  }

}