How to shift focus to the next TextField in Flutter?
Asked Answered
C

11

168

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.

Catapult answered 3/9, 2018 at 13:23 Comment(4)
also in flutter_gallery it is the same. there should be some mechanism though.Catapult
It's not possible as of now(see github.com/flutter/flutter/issues/11344). Which part of flutter_gallery are you talking about?Allative
#51910298Nonah
ohh, i need to find a work around. I meant the flutter_gallery has the same issue.Catapult
C
135

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.

Catapult answered 3/9, 2018 at 14:28 Comment(5)
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.Robbinrobbins
@Harsh what if there are more than two TextFormFields? 6 maybePteridophyte
@fajarainul The logic remain the same. onFieldSubmitted of second field you requestFocus of third fieldCatapult
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.Affix
Instead of FocusScope.of(context).requestFocus(focus); you can simply call focus.requestFocus()Maim
N
319

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.
        ),
      ],
    ),
  );
}
Nosegay answered 28/2, 2020 at 16:41 Comment(5)
this still requires you to click the done button to focus to next input. how about auto focus. auto move to next field?Gentes
@Gentes You can do that too using onChanged property.Nosegay
InputDatePickerFormField doesn't implement the textInputAction :/Boon
nextFocus() will not work if you use "Password hide/show button" next to TextField.Haemo
Useful answer, I used only textInputAction: TextInputAction.next, and it workedHugibert
C
135

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.

Catapult answered 3/9, 2018 at 14:28 Comment(5)
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.Robbinrobbins
@Harsh what if there are more than two TextFormFields? 6 maybePteridophyte
@fajarainul The logic remain the same. onFieldSubmitted of second field you requestFocus of third fieldCatapult
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.Affix
Instead of FocusScope.of(context).requestFocus(focus); you can simply call focus.requestFocus()Maim
I
35

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);
  }
}
Ingrain answered 20/7, 2020 at 23:7 Comment(2)
this almost worked, I had to replace onSubmitted: (_) with onEditingComplete: () to make it work with a more difficult layoutDiplodocus
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.Pheni
E
16

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
        ),
Ersatz answered 2/7, 2020 at 15:26 Comment(2)
It skips fields because TextInputAction.next alone causes the focus to shiftFreudberg
textInputAction: TextInputAction.next, // onFieldSubmitted: (_) => // FocusScope.of(context).nextFocus(), textInputAction: TextInputAction.next, it's enough to shifting the focus. No need of onFieldSubmit.Posterity
M
12

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(),
                          ),
                    )
                  ],
                )
Medication answered 5/8, 2020 at 6:19 Comment(1)
I used your code to set textfield next to each other, I am beginner in flutter. Thanks!Lattie
A
9

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);
  }
}
Ammons answered 20/1, 2022 at 15:2 Comment(0)
W
6

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',
            ),
          )
        ],
      ),
    );
  }
}
Wavell answered 15/1, 2021 at 23:59 Comment(2)
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.Carricarriage
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 :)Wavell
P
5

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,
  )```
Popularly answered 27/4, 2023 at 14:53 Comment(0)
F
2

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),
              ),
Fane answered 19/7, 2020 at 17:51 Comment(0)
J
2

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);
}
Jean answered 24/9, 2020 at 16:52 Comment(0)
L
2

I hope it's useful

textInputAction: TextInputAction.next,
        onEditingComplete: () {
             FocusScope.of(context).nextFocus();
        },
        onSubmitted: (value) {
            if (value.isNotEmpty) {
                  FocusScope.of(context).nextFocus();
            }
        },
Landman answered 7/1, 2023 at 5:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.