How to shift focus to the next TextField in Flutter?
FlutterDartFlutter LayoutFocusTextfieldFlutter Problem Overview
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.
Flutter Solutions
Solution 1 - Flutter
Screenshot:
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 - Flutter
Found a way to achieve it.
-
Displaying Next Icon instead of Done - setting
textInputAction
parameter toTextInputAction.next
-
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 - Flutter
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);
}
}
Solution 4 - Flutter
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
),
Solution 5 - Flutter
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(),
),
)
],
)
Solution 6 - Flutter
I tried by adding just the textInputAction
property, 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',
),
)
],
),
);
}
}
Solution 7 - Flutter
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);
}
}
Solution 8 - Flutter
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),
),
Solution 9 - Flutter
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);
}