My app has a TextField. When editing is done, an _isEdited flag is set which causes a (new) "submit" button to be added to the widget tree. However, this causes the TextField to lose the input focus and cursor, at least for MacOS and web. The user must touch the text field again to continue typing. Yuk!
How can I prevent this and allow the text field to keep the input focus?
The following has the problem:
Widget child = ... textfield ...;
if (_isEdited)
child = Column(children: [child, button]);
but this does not:
Widget child = ... textfield ...;
if (_isEdited)
child = Column(children: [child, button]);
else
child = Column(children: [child]);
It's problematic because I don't want to keep the same widget tree (which is much more complex in practice). I thought providing keys for widgets was supposed to solve this,
TextField(key: const Key("Key1"), ... )
but it seems to make no difference.
I also don't want to simply make the widget invisible since invisible widget take up layout space.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode();
bool _isEdited = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Text Input Form'),
),
body: body(context),
);
}
Widget submitButton(BuildContext context) {
Widget child = ElevatedButton(
onPressed: () {},
child: Text('Save'),
);
child = Padding(padding: EdgeInsets.all(20), child: child);
return child;
}
void onChanged(String) {
setState(() {
_isEdited = true;
});
}
Widget body(BuildContext context) {
Widget child = TextField(
key: const Key("Key1"),
focusNode: _focusNode,
controller: _controller,
onChanged: onChanged);
Widget button = submitButton(context);
if (_isEdited)
child = Column(children: [child, button]);
// else
// child = Column(children: [child]);
return child;
}
}
Update: (see @Nguyen family's answer, below). The following fails,
child =
ValueListenableBuilder<bool>(
valueListenable: isEditing,
builder: (ctx, val, child_) {
if (!val) return child_!;
return Column(children: [
child_!, button]);
},
child: child,
);
but:
child =
ValueListenableBuilder<bool>(
valueListenable: isEditing,
builder: (ctx, val, child_) {
if (!val) return child_!;
return Column(children: [
child_!, button]);
},
child: child,
);
succeeds, presumably also because the text field is in the same position in the widget tree as the successful example above.
My app has a TextField. When editing is done, an _isEdited flag is set which causes a (new) "submit" button to be added to the widget tree. However, this causes the TextField to lose the input focus and cursor, at least for MacOS and web. The user must touch the text field again to continue typing. Yuk!
How can I prevent this and allow the text field to keep the input focus?
The following has the problem:
Widget child = ... textfield ...;
if (_isEdited)
child = Column(children: [child, button]);
but this does not:
Widget child = ... textfield ...;
if (_isEdited)
child = Column(children: [child, button]);
else
child = Column(children: [child]);
It's problematic because I don't want to keep the same widget tree (which is much more complex in practice). I thought providing keys for widgets was supposed to solve this,
TextField(key: const Key("Key1"), ... )
but it seems to make no difference.
I also don't want to simply make the widget invisible since invisible widget take up layout space.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode();
bool _isEdited = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Text Input Form'),
),
body: body(context),
);
}
Widget submitButton(BuildContext context) {
Widget child = ElevatedButton(
onPressed: () {},
child: Text('Save'),
);
child = Padding(padding: EdgeInsets.all(20), child: child);
return child;
}
void onChanged(String) {
setState(() {
_isEdited = true;
});
}
Widget body(BuildContext context) {
Widget child = TextField(
key: const Key("Key1"),
focusNode: _focusNode,
controller: _controller,
onChanged: onChanged);
Widget button = submitButton(context);
if (_isEdited)
child = Column(children: [child, button]);
// else
// child = Column(children: [child]);
return child;
}
}
Update: (see @Nguyen family's answer, below). The following fails,
child =
ValueListenableBuilder<bool>(
valueListenable: isEditing,
builder: (ctx, val, child_) {
if (!val) return child_!;
return Column(children: [
child_!, button]);
},
child: child,
);
but:
child =
ValueListenableBuilder<bool>(
valueListenable: isEditing,
builder: (ctx, val, child_) {
if (!val) return child_!;
return Column(children: [
child_!, button]);
},
child: child,
);
succeeds, presumably also because the text field is in the same position in the widget tree as the successful example above.
Share Improve this question edited Mar 27 at 23:33 user48956 asked Mar 27 at 4:00 user48956user48956 15.8k24 gold badges100 silver badges171 bronze badges 2 |2 Answers
Reset to default 1Maybe you could try this solution. Instead of assign like this:
if (isEditing)
child = Column(children: [child, button]);
else
child = Column(children: [child]);
You could make your _isEdited
as a ValueNotifier:
final ValueNotifier<bool> isEditing = ValueNotifier<bool>(false);
@override
void dispose() {
isEditing.dispose();
}
void onChanged(String) {
isEditing.value = true;
}
Widget body(BuildContext context) {
return Column(
children: [
TextField(),
ValueListenableBuilder<bool>(
valueListenable: isEditing,
builder: (ctx, val, child) {
if (!val) return SizedBox();
return SubmitButton();
},
),
],
);
}
By this way, your body
won't be rebuild again. I think your TextField still can keep the focus status.
Try this. If you have any question, u can comment under my answer.
Using
# Nope: Key("") # ValueKey # ObjectKey #UniqueKey
final textKey = GlobalKey();
...
child = TextField(
key: textKey=
resolves this.
TextField.autofocus
could help – pskink Commented Mar 27 at 6:07