How to prevent Textfield rebuild when using the state of its FocusNode? - flutter

I have a stateful widget like this:
class _CustomTextField extends StatefulWidget {
const _CustomTextField();
#override
State<ConsumerStatefulWidget> createState() =>
__CustomTextFieldState();
}
class __CustomTextFieldState extends State<_CustomTextField> {
late final FocusNode focusNode;
#override
void initState() {
super.initState();
focusNode = FocusNode()..addListener(() {
setState(() {});
});
}
#override
void dispose() {
focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Row(
color: focusNode.hasFocus ? const Color(0xFFF5F5F5) : null,
children: [
if(focusNode.hasFocus)
Icon(Icons.edit),
TextField(
focusNode: focusNode,
),
]
);
}
}
where I want to change the widget when the textfield gets selected.
My problem is, that with the current implementation, the whole widget gets rebuilt, causing the Keyboard to disappear immediately after selecting the textfield.
What is the best way to handle this?

Related

How to listen to a FocusNode with GetX in Flutter

I've just discovered GetX and I love it! Is there a neat way of listening to a FocusNode with GetX instead of using a StatefulWidget?
class CustomTextField extends StatefulWidget {
const CustomTextField({Key? key}) : super(key: key);
#override
_CustomTextFieldState createState() => _CustomTextFieldState();
}
class _CustomTextFieldState extends State<CustomTextField> {
final FocusNode _focusNode = FocusNode();
#override
void initState() {
_focusNode.addListener(() => setState(() {}));
super.initState();
}
#override
void dispose() {
_focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return CustomContainer(
isFocused: _focusNode.hasFocus,
child: TextField(
focusNode: _focusNode,
),
);
}
}
you can create a StatelessWidget or a GetView<MyController> and add the focus node in the controller
class MyController extends GetXController {
final FocusNode focusNode = FocusNode();
#override
void onInit() {
focusNode.addListener((){ ...do some stuff }));
super.onInit();
}
#override
onClose() {
focusNode.dispose();
super.onClose();
}
}
Then you can use this in your widget
...
#override
Widget build(BuildContext context) {
return CustomContainer(
isFocused: controller.focusNode.hasFocus,
child: TextField(
focusNode: controller.focusNode.focusNode,
),
);
}

TextField input doubled

In a Flutter desktop for Windows project I have a TextField widget in a statefulWidget with a controller attached to it.
late TextEditingController searchController;
#override
void initState() {
super.initState();
searchController = TextEditingController();
}
#override
void dispose() {
searchController.dispose();
super.dispose();
}
TextField(
keyboardType: TextInputType.text,
controller: searchController,
decoration: defaultTextFieldDecoration.copyWith(hintText: "Type to Search"),
style: textFieldStyle,
onChanged: (value) {},
),
Now when I type something into the textfield like "abc" every key gets input twice like "aabbcc" and I can't figure out why. I have used TextFields many times and that never happended.
It is also not a problem with my keyboard since I can type this without problems :D
Edit: Here is a full example to reproduce this problem.
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: Main()));
}
class Main extends StatefulWidget {
const Main({Key? key}) : super(key: key);
#override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
#override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: TextFieldTestWidget(),
),
);
}
}
class TextFieldTestWidget extends StatefulWidget {
const TextFieldTestWidget({Key? key}) : super(key: key);
#override
_TextFieldTestWidgetState createState() => _TextFieldTestWidgetState();
}
class _TextFieldTestWidgetState extends State<TextFieldTestWidget> {
TextEditingController controller = TextEditingController();
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: 300,
height: 100,
child: TextField(
controller: controller,
),
);
}
}
Edit: Added Image
Edit again:
Found that it has something to do with the initial text value... I just dont't get what exactly. When i change the TextEditingController to TextEditingController(text:"") it works somehow. I think instancing TextEditingControllers is somehow broken.
Upgrading Flutter from beta 2.4.0-4.2.pre -> beta 2.5.0-5.1.pre fixed it for now. At least it wasn't an error in my code :D

Focus first focusable element below a Focus widget

I am trying to focus the first focusable element below a Focus widget. Consider the following artifical example. It contains a BarWidget with some TextField.
The FooWidget wraps it into a Focus widget with the given focusNode. After one second, I'd like to focus the first element of BarWidget. Please not that I don't want to pass a FocusNode down into BarWidget.
import 'package:flutter/material.dart';
void main() {
runApp(Foo());
}
class Foo extends StatefulWidget {
#override
State createState() => FooState();
}
class FooState extends State<Foo> {
final focusNode = FocusNode();
void initState() {
super.initState();
Future.delayed(
const Duration(seconds: 1),
focusNode.requestFocus,
);
}
#override
Widget build(BuildContext context) => MaterialApp(
home: Scaffold(
body: Focus(
focusNode: focusNode,
child: BarWidget(),
),
),
);
}
class BarWidget extends StatelessWidget {
#override
Widget build(BuildContext context) => Column(
children: [
Text("Foo"),
TextField(),
Text("Bar"),
],
);
}
It is in theory possible by using FocusTraversalPolicy.sortDescendants. However, that method is declared as #protected. See: https://github.com/flutter/flutter/issues/70534
Full example:
import 'package:flutter/material.dart';
void main() {
runApp(Foo());
}
class Foo extends StatefulWidget {
#override
State createState() => FooState();
}
class FooState extends State<Foo> {
final focusNode = FocusNode(canRequestFocus: false);
void initState() {
super.initState();
void f() {
final policy = FocusTraversalGroup.of(focusNode.context);
// ignore: invalid_use_of_protected_member
final node = policy.sortDescendants(focusNode.traversalDescendants, null).first;
node.requestFocus();
}
Future.delayed(
const Duration(seconds: 1),
f,
);
}
#override
Widget build(BuildContext context) => MaterialApp(
home: Scaffold(
body: Focus(
focusNode: focusNode,
child: BarWidget(),
),
),
);
}
class BarWidget extends StatelessWidget {
#override
Widget build(BuildContext context) => Column(
children: [
Text("Foo"),
TextField(),
Text("Bar"),
],
);
}

How to set the focus on a materialbutton in flutter

I want the focus the focus on the material button so I can press enter or click the button an create an item
final FocusNode _createButtonFocusNode = new FocusNode();
#override
void initState() {
FocusScope.of(context).requestFocus(_createButtonFocusNode);
super.initState();
}
RawKeyboardListener(
focusNode: _createButtonFocusNode,
onKey: (RawKeyEvent event) {
if (event.logicalKey == LogicalKeyboardKey.enter) {
_createItem();
}
},
child:RaisedButton(focusNode: _createButtonFocusNode,
onPressed: () {
_createItem();
},
child: Text("Create"))))
Assume also a cancel material button exists with a _cancelItem event that should be able to accept an enter key on focus
You can copy paste run full code below
You can use _node.requestFocus() to request focus and list keyboard event with FocusAttachment and attach
In demo code, when receive Enter will change button color, see working demo below
code snippet
_node.requestFocus();
...
FocusAttachment _nodeAttachment;
_nodeAttachment = _node.attach(context, onKey: _handleKeyPress);
...
bool _handleKeyPress(FocusNode node, RawKeyEvent event) {
if (event is RawKeyDownEvent) {
print('Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
if (event.logicalKey == LogicalKeyboardKey.enter) {
print('clicked enter');
setState(() {
_color = Colors.deepPurple;
});
return true;
}
}
return false;
}
working demo
full code
// Flutter code sample for FocusNode
// This example shows how a FocusNode should be managed if not using the
// [Focus] or [FocusScope] widgets. See the [Focus] widget for a similar
// example using [Focus] and [FocusScope] widgets.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: MyStatelessWidget(),
),
);
}
}
class CustomButton extends StatefulWidget {
FocusNode focusNode;
CustomButton({Key key, this.focusNode}) : super(key: key);
#override
_CustomButtonState createState() => _CustomButtonState();
}
class _CustomButtonState extends State<CustomButton> {
bool _focused = false;
FocusAttachment _nodeAttachment;
Color _color = Colors.white;
#override
void initState() {
super.initState();
//widget.focusNode = FocusNode(debugLabel: 'Button');
widget.focusNode.addListener(_handleFocusChange);
_nodeAttachment = widget.focusNode.attach(context, onKey: _handleKeyPress);
}
void _handleFocusChange() {
print(widget.focusNode.hasFocus);
if (widget.focusNode.hasFocus != _focused) {
setState(() {
_focused = widget.focusNode.hasFocus;
_color = Colors.white;
});
}
}
bool _handleKeyPress(FocusNode node, RawKeyEvent event) {
if (event is RawKeyDownEvent) {
print('Focus node ${node.debugLabel} got key event: ${event.logicalKey}');
if (event.logicalKey == LogicalKeyboardKey.enter) {
print('clicked enter');
setState(() {
_color = Colors.deepPurple;
});
return true;
}
}
return false;
}
#override
void dispose() {
widget.focusNode.removeListener(_handleFocusChange);
// The attachment will automatically be detached in dispose().
widget.focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
_nodeAttachment.reparent();
return Center(
child: RaisedButton(
focusNode: widget.focusNode,
color: _focused ? _color : Colors.white,
child: Text(_focused ? "focused" : 'Not focus'),
onPressed: () {
print("create item");
},
),
);
}
}
class MyStatelessWidget extends StatefulWidget {
MyStatelessWidget({Key key}) : super(key: key);
#override
_MyStatelessWidgetState createState() => _MyStatelessWidgetState();
}
class _MyStatelessWidgetState extends State<MyStatelessWidget> {
FocusNode _node1 = FocusNode();
FocusNode _node2 = FocusNode();
#override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
return DefaultTextStyle(
style: textTheme.headline4,
child: Column(
children: [
CustomButton(
focusNode: _node1,
),
CustomButton(
focusNode: _node2,
),
RaisedButton(
onPressed: () {
_node1.requestFocus();
setState(() {});
},
child: Text("request focus button 1")),
RaisedButton(
onPressed: () {
_node2.requestFocus();
setState(() {});
},
child: Text("request focus button 2")),
],
),
);
}
}
If all you want is for the button to be focused by default, you can do that by just specifying autofocus:true on the button, and you don't even need to create a FocusNode:
class MyCustomWidget extends StatelessWidget {
const MyCustomWidget({Key? key}) : super(key: key);
void _createItem() {
print('Item created');
}
#override
Widget build(BuildContext context) {
return TextButton(
autofocus: true,
child: const Text('CREATE'),
onPressed: _createItem,
);
}
}
This will automatically focus the widget when first built, as long as something else doesn't have the focus already.
If you need to set the focus from another control, you can do that with a focus node, but you don't need to use a FocusAttachment (you rarely, if ever, need to use one of those), you can just pass it to the button and call requestFocus() on it.
class MyCustomWidget extends StatefulWidget {
const MyCustomWidget({Key? key}) : super(key: key);
#override
State<MyCustomWidget> createState() => _MyCustomWidgetState();
}
class _MyCustomWidgetState extends State<MyCustomWidget> {
late FocusNode _createButtonFocusNode;
#override
void initState() {
super.initState();
_createButtonFocusNode = FocusNode();
}
#override
void dispose() {
_createButtonFocusNode.dispose();
super.dispose();
}
void _createItem() {
print('Item created');
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
child: const Text('FOCUS OTHER BUTTON'),
onPressed: () => _createButtonFocusNode.requestFocus(),
),
TextButton(
focusNode: _createButtonFocusNode,
child: const Text('CREATE'),
onPressed: _createItem,
),
],
),
);
}
}
(When you do create a FocusNode, be sure to dispose of it properly.)

Keep keyboard open when temporarily disabling text field

How to keep the keyboard on screen while temporarily disabling a text field?
CupertinoTextField dismisses the keyboard when enabled=false or readOnly=true. I need to keep the keyboard on screen.
I searched for about four hours and finally came up with a solution: Have the text field's onChanged function focus a hidden widget that accepts keyboard input. Once the processing is complete, focus the text field again.
Working example:
import 'package:flutter/cupertino.dart'
show
CupertinoApp,
CupertinoButton,
CupertinoPageScaffold,
CupertinoTextField;
import 'package:flutter/widgets.dart'
show
BuildContext,
Center,
ClipRect,
Column,
Container,
FocusNode,
FocusScope,
MainAxisSize,
runApp,
State,
StatefulWidget,
Text,
TextAlign,
TextEditingController,
Widget;
import 'package:meta/meta.dart' show required;
class KeepKeyboardOnScreen extends StatefulWidget {
final FocusNode focusNode;
const KeepKeyboardOnScreen({#required this.focusNode});
#override
State createState() => KeepKeyboardOnScreenState();
}
class KeepKeyboardOnScreenState extends State<KeepKeyboardOnScreen> {
TextEditingController _controller;
#override
void initState() {
super.initState();
_controller = new TextEditingController();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => Container(
height: 0,
child: ClipRect(
child: CupertinoTextField(
controller: _controller,
focusNode: widget.focusNode,
onChanged: (_) => _controller.clear(),
),
),
);
}
class Page extends StatefulWidget {
#override
State createState() => PageState();
}
class PageState extends State<Page> {
TextEditingController _controller;
FocusNode _focusNode;
FocusNode _keepKeyboardOnScreenFocusNode;
bool enabled = true;
#override
void initState() {
super.initState();
_controller = new TextEditingController();
_focusNode = new FocusNode();
_keepKeyboardOnScreenFocusNode = new FocusNode();
}
#override
void dispose() {
_controller.dispose();
_focusNode.dispose();
_keepKeyboardOnScreenFocusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => CupertinoApp(
home: CupertinoPageScaffold(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CupertinoTextField(
controller: _controller,
focusNode: _focusNode,
enabled: enabled,
),
KeepKeyboardOnScreen(focusNode: _keepKeyboardOnScreenFocusNode),
CupertinoButton(
onPressed: () {
setState(() {
enabled = true;
});
FocusScope.of(context).requestFocus(_focusNode);
},
child: Text("Enable", textAlign: TextAlign.center)),
CupertinoButton(
onPressed: () {
setState(() {
enabled = false;
});
FocusScope.of(context)
.requestFocus(_keepKeyboardOnScreenFocusNode);
},
child: Text("Disable", textAlign: TextAlign.center)),
],
),
),
),
);
}
void main() async {
runApp(Page());
}
See also: Flutter Issue #45076 Add high-level documentation and examples on managing keyboard focus.