180

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.

4
  • also in flutter_gallery it is the same. there should be some mechanism though. Commented Sep 3, 2018 at 13:24
  • It's not possible as of now(see github.com/flutter/flutter/issues/11344). Which part of flutter_gallery are you talking about? Commented Sep 3, 2018 at 13:27
  • stackoverflow.com/questions/51909798/… Commented Sep 3, 2018 at 13:57
  • ohh, i need to find a work around. I meant the flutter_gallery has the same issue. Commented Sep 3, 2018 at 14:00

12 Answers 12

338

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.
        ),
      ],
    ),
  );
}
Sign up to request clarification or add additional context in comments.

5 Comments

this still requires you to click the done button to focus to next input. how about auto focus. auto move to next field?
@Yuhao You can do that too using onChanged property.
InputDatePickerFormField doesn't implement the textInputAction :/
nextFocus() will not work if you use "Password hide/show button" next to TextField.
Useful answer, I used only textInputAction: TextInputAction.next, and it worked
139

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.

5 Comments

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.
@Harsh what if there are more than two TextFormFields? 6 maybe
@fajarainul The logic remain the same. onFieldSubmitted of second field you requestFocus of third field
This solution has a bug. If you press tab, then tab not only changes focus, but is added to as text to the form field - this is not normal behavior for a form. Tab should change focus, or enter text, never both.
Instead of FocusScope.of(context).requestFocus(focus); you can simply call focus.requestFocus()
37

These are 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 the 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);
  }
}

3 Comments

this almost worked, I had to replace onSubmitted: (_) with onEditingComplete: () to make it work with a more difficult layout
Spent almost 3 hours looking into this issue. Thank you for this. Login form never had the issue as email field has no icon and password with the button was set to unfocus but when doing my password reset with old and 2 new password fields standard focusNext would close keyboard every time until I found this.
Should be: while (FocusScope.of(this).focusedChild!.context == null);
19

For TextFormFeild use can use onFieldSubmitted

TextFormField(
          decoration: InputDecoration(hintText: "Username"),
          textInputAction: TextInputAction.next,
          onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(), // focus to next
        ),
TextFormField(
          decoration: InputDecoration(hintText: "Password"),
          textInputAction: TextInputAction.done,
          onFieldSubmitted: (_) => FocusScope.of(context).unfocus(), // Unfocus and hide keyboard
        ),

Don't know the exact reason but onFieldSubmitted sometimes skips one or more fields in that case onEditingComplete works as expected

TextFormField(
          decoration: InputDecoration(hintText: "Username"),
          textInputAction: TextInputAction.next,
          onEditingComplete : (_) => FocusScope.of(context).nextFocus(), // focus to next
        ),
TextFormField(
          decoration: InputDecoration(hintText: "Password"),
          textInputAction: TextInputAction.done,
          onEditingComplete : (_) => FocusScope.of(context).unfocus(), // Unfocus and hide keyboard
        ),

2 Comments

It skips fields because TextInputAction.next alone causes the focus to shift
textInputAction: TextInputAction.next, // onFieldSubmitted: (_) => // FocusScope.of(context).nextFocus(), textInputAction: TextInputAction.next, it's enough to shifting the focus. No need of onFieldSubmit.
14

For me this worked it moves to next input on entering first digit

Row(
                  children: <Widget>[
                    Expanded(
                      child: TextFormField(
                        textInputAction: TextInputAction.next,
                        onChanged: (_) => FocusScope.of(context).nextFocus(),
                          controller:c1 ,)
                    ),
                    SizedBox(
                      width: 20.0,
                    ),
                    Expanded(
                      child: TextFormField(
                        textInputAction: TextInputAction.next,
                        onChanged: (_) => FocusScope.of(context).nextFocus(),
                          controller:c2 ,),
                    ),
                    SizedBox(
                      width: 20.0,
                    ),
                    Expanded(
                      child: TextFormField(
                          controller:c3 ,
                        textInputAction: TextInputAction.next,
                        onChanged: (_) => FocusScope.of(context).nextFocus(),),
                    ),
                    SizedBox(
                      width: 20.0,
                    ),
                    Expanded(
                      child: TextFormField(
                          controller:c4 ,
                        textInputAction: TextInputAction.next,
                        onChanged: (_) => FocusScope.of(context).nextFocus(),),
                    ),
                    SizedBox(
                      width: 20.0,
                    ),
                    Expanded(
                      child: TextFormField(

                          controller:c5 ,
                        textInputAction: TextInputAction.next,
                        onChanged: (_) => FocusScope.of(context).nextFocus(),),
                    ),
                    SizedBox(
                      width: 20.0,
                    ),
                    Expanded(
                      child: TextFormField(
                          controller:c6 ,
                        textInputAction: TextInputAction.next,
                        onChanged: (_) => FocusScope.of(context).unfocus(),
                          ),
                    )
                  ],
                )

1 Comment

I used your code to set textfield next to each other, I am beginner in flutter. Thanks!
11

Thanks for the extension shared by @haytham-anmar and the extension from @natesh-bhat!

But this will not work anymore for the future release of Flutter due to a breaking change on EditableText tree (ref.: Move text editing Actions to EditableTextState #90684).

Please consider the following migration code:

Before migration:

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

After migration:

extension Utility on BuildContext {
  void nextEditableTextFocus() {
    do {
      FocusScope.of(this).nextFocus();
    } while (FocusScope.of(this).focusedChild!.context == null);
  }
}

Comments

7

I tried by adding just the textInputActionproperty, and it worked without anything else.

Maybe newer versions of Flutter have improved this functionality? Or StatefulWidget & FormKey are needed to have this working?

I'm on Flutter (Channel stable, 1.22.5)

Give it a try:

class MyForm extends StatefulWidget {
  @override
  State createState() => _myFormState();
}

class _myFormState extends State<MyForm> {
  //
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            // *** Just added this
            textInputAction: TextInputAction.next,
            decoration: const InputDecoration(
              labelText: 'Input 1',
            ),
          ),
          TextFormField(
            textInputAction: TextInputAction.done,
            decoration: const InputDecoration(
              labelText: 'Input 2',
            ),
          )
        ],
      ),
    );
  }
}

2 Comments

You are right, I think it has been fixed here. Actually it is now an error to call nextFocus if you have set textInputAction to TextInputAction.next, it would cause skipping one field. So that was a breaking change.
I don't know, the return key-different action setting exists since many years (more than 10 in iOS). But I cannot tell you the same about Flutter support, maybe the behaviour has been "mapped" recently. Anyway, as long as one doesn't need particular behaviours, I would stick to using textInputAction, as it the default for an application, and users are accustomed to that :)
6

In the latest flutter version, All the above ways do not work if you have a visible icon or any other icon in your form field. You can use this way to make it work

Create FocusNode for each of your TextFormFields, assign it to the TextFormFields and onEditingComplete, request focus to the next Node.

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

  // ...

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

Comments

3

I used onSubmitted instead of onFieldSubmitted

Example code

      TextField(
                textInputAction: TextInputAction.next,
                onSubmitted: (_) => FocusScope.of(context).nextFocus(),
                controller: _phoneController,
                decoration: const InputDecoration(
                  labelText: 'Phone number',
                ),
                style: TextStyle(fontSize: 16.0, color: Colors.white),
              ),

Comments

3

You can use this helper function to focus the next text field :

void focusNextTextField(BuildContext context) {
  do {
    var foundFocusNode = FocusScope.of(context).nextFocus();
    if (!foundFocusNode) return;
  } while (FocusScope.of(context).focusedChild.context.widget is! EditableText);
}

Comments

3

I hope it's useful

textInputAction: TextInputAction.next,
        onEditingComplete: () {
             FocusScope.of(context).nextFocus();
        },
        onSubmitted: (value) {
            if (value.isNotEmpty) {
                  FocusScope.of(context).nextFocus();
            }
        },

Comments

2

I recently encountered this problem, the solution above, with manually capturing the necessary focus with nextFocus(), will solve the problem, but not the root cause. I think in most cases the problem is that the screen contains icons (for example, the field clear icon) that also capture focus, so the correct solution is to minimize all such widgets in ExcludeFocus, this will allow you to switch to the next one automatically.

DartPad with example

              // Works with ExcludeFocus
              TextFormField(
                textInputAction: TextInputAction.next,
                decoration: InputDecoration(
                  suffixIcon: ExcludeFocus(
                    child: IconButton(
                      icon: const Icon(Icons.close),
                      onPressed: () {},
                    ),
                  ),
                ),
              ),
              // Does not work because the focus is taken away by the icon
              TextFormField(
                textInputAction: TextInputAction.next,
                decoration: InputDecoration(
                  suffixIcon: IconButton(
                    icon: const Icon(Icons.close),
                    onPressed: () {},
                  ),
                ),
              ),

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.