how to focus the next text field automatically after user input 1 character in flutter

875

Solution 1

This can be done in Flutter in different ways, and I'll try to share the simplest one of them. Before getting into the answer, it's worth mentioning the following issue:

In Flutter, backspace does not send any event when the TextField is empty (i.e. TextField.onChanged won't be called). In your case, if the user is at third field and they press backspace to return to the second field, there's no way to capture that key press without some workaround that were discussed in the linked issue. In short, you'll need to add a zero-width space character (it doesn't get rendered but is present in the String) to detect backspace events.

I mentioned this issue because I'm sharing an example that utilize the zero-width space character (zwsp for short).

In the following example, I simply created two lists that contains:

  • FocusNode for each field
  • TextEditingController for each field.

Based on the index, you can bring the focus to a specific field by calling: FocusNode.requestFocus().

Similarly, you can remove the focus by calling FocusNode.unfocus or you can remove any focus from anywhere by calling: FocusScope.of(context).unfocus(); (in the example below, it's used after the last character is inserted to hide the keyboard).

That being said, here's a full example that you can copy and paste to try it out:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;
  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(child: CodeField()),
    );
  }
}

/// zero-width space character
///
/// this character can be added to a string to detect backspace.
/// The value, from its name, has a zero-width so it's not rendered
/// in the screen but it'll be present in the String.
///
/// The main reason this value is used because in Flutter mobile,
/// backspace is not detected when there's nothing to delete.
const zwsp = '\u200b';

// the selection is at offset 1 so any character is inserted after it.
const zwspEditingValue = TextEditingValue(text: zwsp, selection: TextSelection(baseOffset: 1, extentOffset: 1));

class CodeField extends StatefulWidget {
  const CodeField({Key key}) : super(key: key);

  @override
  _CodeFieldState createState() => _CodeFieldState();
}

class _CodeFieldState extends State<CodeField> {
  List<String> code = ['', '', '', ''];

  List<TextEditingController> controllers;
  List<FocusNode> focusNodes;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    focusNodes = List.generate(4, (index) => FocusNode());
    controllers = List.generate(4, (index) {
      final ctrl = TextEditingController();
      ctrl.value = zwspEditingValue;
      return ctrl;
    });

    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      // give the focus to the first node.
      focusNodes[0].requestFocus();
    });
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    focusNodes.forEach((focusNode) {
      focusNode.dispose();
    });
    controllers.forEach((controller) {
      controller.dispose();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: List.generate(
        4,
        (index) {
          return Container(
            width: 20,
            height: 20,
            margin: const EdgeInsets.all(10),
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(10),
            ),
            child: TextField(
              controller: controllers[index],
              focusNode: focusNodes[index],
              maxLength: 2,
              keyboardType: TextInputType.number,
              decoration: InputDecoration(
                counterText: "",
              ),
              onChanged: (value) {
                if (value.length > 1) {
                  // this is a new character event
                  if (index + 1 == focusNodes.length) {
                    // do something after the last character was inserted
                    FocusScope.of(context).unfocus();
                  } else {
                    // move to the next field
                    focusNodes[index + 1].requestFocus();
                  }
                } else {
                  // this is backspace event

                  // reset the controller
                  controllers[index].value = zwspEditingValue;
                  if (index == 0) {
                    // do something if backspace was pressed at the first field

                  } else {
                    // go back to previous field
                    controllers[index - 1].value = zwspEditingValue;
                    focusNodes[index - 1].requestFocus();
                  }
                }
                // make sure to remove the zwsp character
                code[index] = value.replaceAll(zwsp, '');
                print('current code = $code');
              },
            ),
          );
        },
      ),
    );
  }
}


Solution 2

You may want to use a FocusNode on each of your TextFormField, this way, once your user has enter text in the TextFormField, you can use in the callback onChanged of the TextFormField call myNextTextFieldFocusNode.requestFocus()


  FocusNode textFieldOne = FocusNode();
  FocusNode textFieldTwo = FocusNode();

  // ...

  TextFormField(
        onChanged: (_) {
           textFieldTwo.requestFocus();
        },
        focusNode: textFieldOne,
        controller: textController,
  )

Solution 3

You can use onChanged and nodefocus properties. When onchanged called refer to next textfield.

init a focus node ;

  late FocusNode myFocusNode;

  @override
  void initState() {
    super.initState();

    myFocusNode = FocusNode();
  }

  @override
  void dispose() {
    // Clean up the focus node when the Form is disposed.
    myFocusNode.dispose();

    super.dispose();
  }

onChanged property;

TextField(
  focusNode: myFocusNode1,
  onChanged: (text) {

   myFocusNode2.requestFocus();// I could not remember the correct usage please check
  },
),
Share:
875
Heng YouSour
Author by

Heng YouSour

Updated on December 30, 2022

Comments

  • Heng YouSour
    Heng YouSour over 1 year

    I have 4 textFormField widgets. Once the user has completed the first text field I would like to focus on the next textField automatically. Is there a way to do this in Flutter? anyone please share , thank in advance :)

    enter image description here