How to shift focus to the next TextField in Flutter?

I am new to Flutter.

I am building a form with multiple text inputs using following widgets: Form, TextFormField. The keyboard that appears doesn't show "next" (which should shift the focus to next field) field action instead it is "done" action (which hides the keyborad).

I looked for any hints in official docs, found nothing directly that can be done. I although landed on FocusNode(cookbook, api doc). It provides with mechanism to shift focus by some button or any other action on app, but I want it to be in keyboard.


Solution 1:

Screenshot:

enter image description here


Just use:

textInputAction: TextInputAction.next: To move the cursor to the next field.

textInputAction: TextInputAction.done: To close the keyboard.

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Column(
      children: <Widget>[
        TextField(
          decoration: InputDecoration(hintText: 'TextField A'),
          textInputAction: TextInputAction.next, // Moves focus to next.
        ),
        TextField(
          decoration: InputDecoration(hintText: 'TextField B'),
          textInputAction: TextInputAction.next, // Moves focus to next.
        ),
        TextField(
          decoration: InputDecoration(hintText: 'TextField C'),
          textInputAction: TextInputAction.done, // Hides the keyboard.
        ),
      ],
    ),
  );
}

Solution 2:

Found a way to achieve it.

  1. Displaying Next Icon instead of Done - setting textInputAction parameter to TextInputAction.next

  2. Using onFieldSubmitted callback to request focus node of next field.

    class FormWidget extends StatelessWidget{    
       final focus = FocusNode();
       @override
       Widget build(BuildContext context) {
        return Form(
          child: SingleChildScrollView(
            padding: EdgeInsets.symmetric(horizontal: 16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                TextFormField(
                  textInputAction: TextInputAction.next,
                  autofocus: true,
                  decoration: InputDecoration(labelText: "Input 1"),
                  onFieldSubmitted: (v){
                    FocusScope.of(context).requestFocus(focus);
                  },
                ),
                TextFormField(
                  focusNode: focus,
                  decoration: InputDecoration(labelText: "Input 2"),
                ),
              ],
            ),
          ),
        );
      }
    }
    

Edit: As stated in the documentation (flutter.io/docs/cookbook/forms/focus), - we also need to manage FocusNode lifecycle. So, init FocusNode in the init() method and dispose in dispose() of the parent Widget. - @AntonDerevyanko

Update: The same can be achieved without FocusNode and FocusScopeNode, by simply calling FocusScope.of(context).nextFocus(), take a look at CopsOnRoad solution on how to do that. For more info check doc.

Solution 3:

This is additional steps to CopsOnRoad answer since it doesn't work in more complex UI when there are focusable widgets in between text fields, for example:

  • when password field has a clickable toggle icon
  • when there is a button (or some other focusable widget) between fields...

the solution here is to keep calling 'nextFocus()' until 'EditableText' is found

   @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: Column(
          children: <Widget>[
            TextField(
              decoration: InputDecoration(hintText: "TextField A"),
              textInputAction: textInputAction1,
              onSubmitted: (_) => context.nextEditableTextFocus(), // move focus to next
            ),
            TextField(
              decoration: InputDecoration(hintText: "TextField B"),
              textInputAction: textInputAction2,
              onSubmitted: (_) => context.nextEditableTextFocus(), // move focus to next
            ),
            MaterialButton(
             onPressed: () {},
             color: Colors.amber,
            ),
            TextField(
              decoration: InputDecoration(hintText: "TextField C"),
              textInputAction: textInputAction3,
              onSubmitted: (_) => FocusScope.of(context).unfocus(), // submit and hide keyboard
            ),
          ],
        ),
      );
    }

Where the extension method is:

extension Utility on BuildContext {
  void nextEditableTextFocus() {
    do {
      FocusScope.of(this).nextFocus();
    } while (FocusScope.of(this).focusedChild?.context?.widget is! EditableText);
  }
}