Reputation: 10865
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.
Upvotes: 176
Views: 140154
Reputation: 561
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:
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);
}
}
Upvotes: 36
Reputation: 113
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,
)```
Upvotes: 5
Reputation: 739
I hope it's useful
textInputAction: TextInputAction.next,
onEditingComplete: () {
FocusScope.of(context).nextFocus();
},
onSubmitted: (value) {
if (value.isNotEmpty) {
FocusScope.of(context).nextFocus();
}
},
Upvotes: 2
Reputation: 146
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);
}
}
Upvotes: 10
Reputation: 4844
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
),
Upvotes: 18
Reputation: 267444
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.
),
],
),
);
}
Upvotes: 332
Reputation: 1830
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',
),
)
],
),
);
}
}
Upvotes: 6
Reputation: 13192
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);
}
Upvotes: 2
Reputation: 1439
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(),
),
)
],
)
Upvotes: 13
Reputation: 11457
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),
),
Upvotes: 2
Reputation: 10865
Found a way to achieve it.
Displaying Next Icon instead of Done - setting textInputAction
parameter to TextInputAction.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.
Upvotes: 137