101
votes

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.

8
also in flutter_gallery it is the same. there should be some mechanism though.Harsh Bhikadia
It's not possible as of now(see github.com/flutter/flutter/issues/11344). Which part of flutter_gallery are you talking about?Mrigank
ohh, i need to find a work around. I meant the flutter_gallery has the same issue.Harsh Bhikadia

8 Answers

170
votes

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.
        ),
      ],
    ),
  );
}
101
votes

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.

21
votes

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);
  }
}
9
votes

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(),
                          ),
                    )
                  ],
                )
9
votes

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
        ),
5
votes

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',
            ),
          )
        ],
      ),
    );
  }
}
1
votes

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),
              ),
0
votes

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);
}