Is it okay to use TextEditingController in StatelessWidget in Flutter? - flutter

I don't need to do many things with TextEditingController but want to show the initial text. And I feel like creating StatefulWidget is too much for that.
Here's what I want my code looks like
// In StatelessWidget
TextField(
controller: TextEditingController(),
)
But every tutorials and blog posts I've seen use TextEditingController in StatefulWidget and dispose them in the dispose method. But I can't dispose them if I use like the above

If you want to use TextEditingController, there is no way around except to use a StatefulWidget if you want to avoid memory leaks.
However, if you see alot of boilerplate in this approach, you can use HookWidget (flutter_hooks) which gives you access to TextEditingController in a simple way and disposes it for you,here is a comparison:
using StatefulWidget:
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
TextEditingController controller;
FocusNode focusNode;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: TextField(
focusNode: focusNode,
controller: controller,
),
),
),
);
}
#override
void initState() {
controller = TextEditingController();
focusNode = FocusNode();
super.initState();
}
#override
void dispose() {
controller.dispose();
focusNode.dispose();
super.dispose();
}
}
using HookWidget:
class Test extends HookWidget {
#override
Widget build(BuildContext context) {
final focusNode = useFocusNode();
final controller = useTextEditingController();
return Scaffold(
body: Center(
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: TextField(
focusNode: focusNode,
controller: controller,
),
),
),
);
}
}

Related

How to move the focus smoothly from a TextField to a newly created TextField?

I'd like to create a screen in which many TextFIelds lined up vertically, and when the Enter key is pressed while editing the bottom TextField, a new TextField is added below it moving the focus too. I created a demo app referring to the example in the docs of FocusNode and it works basically but the keyboard bounces when moving the focus to a newly created TextField (see the gif below). How can I fix this unwanted behavior?
The gif of the demo app
The code of the demo app is here:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int focusedChild = 0;
List<Widget> children = <Widget>[];
List<FocusNode> childFocusNodes = <FocusNode>[];
#override
void initState() {
super.initState();
// Add the first child.
_addChild();
}
#override
void dispose() {
for (final FocusNode node in childFocusNodes) {
node.dispose();
}
super.dispose();
}
void _addChild() {
// Calling requestFocus here creates a deferred request for focus, since the
// node is not yet part of the focus tree.
childFocusNodes
.add(FocusNode(debugLabel: 'Child ${children.length}')..requestFocus());
children.add(
TextField(
focusNode: childFocusNodes.last,
textInputAction: TextInputAction.unspecified,
minLines: 1,
onSubmitted: (value) {
setState(() {
focusedChild = children.length;
_addChild();
});
},
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: children,
),
),
),
);
}
}
Screenshot
You need to use TextInputAction
TextInputAction.next: Moves the focus to the next focusable item.
TextInputAction.done: To close the keyboard.
class TestWidget extends StatelessWidget {
const TestWidget({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: const [
TextField(
decoration: InputDecoration(hintText: 'TextField #1 with next'),
textInputAction: TextInputAction.next, // Moves the focus to the next focusable item.
),
TextField(
decoration: InputDecoration(hintText: 'TextField #2 with next'),
textInputAction: TextInputAction.next, // Moves the focus to the next focusable item.
),
TextField(
decoration: InputDecoration(hintText: 'TextField #3 with done'),
textInputAction: TextInputAction.done, // Close the keyboard.
),
],
),
),
);
}
}
Update
Create a new text field on editing complete and change focus to the new one.
You need to use onEditingComplete function, instead of onSubmitted. Because onEditingComplete will not close (dismiss) the keyboard.
I rewrite your code, and remove generating a list of widgets. And replaced it with TextEditingController because it's a bad experience to keep UI (widgets) in a variable. So text fields generates by TextEditingController length.
I remove a list of FocusNode, and use FocusScope.of(context).nextFocus() to change the focus.
After creating a new TextEditingController function call delayed nextFocus() with delay, to give time to recreate the new UI.
here is the full code:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(home: MyStatefulWidget());
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<TextEditingController> _controllers = [
TextEditingController()
]; // with first controller
_addController() {
setState(() {
_controllers.add(TextEditingController());
});
Future.delayed(const Duration(milliseconds: 100), () {
// Add delay for recreate UI after setState
FocusScope.of(context).nextFocus();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: _controllers
.map((e) => TextField(
textInputAction: TextInputAction.next,
controller: e,
onEditingComplete: _addController,
))
.toList(),
),
),
),
);
}
}

How to continuously get whether the TextField's text is empty in Flutter?

I have a TextField. I want its text not to be empty. (so I want to know if the text is empty)
I have tried using the following code, but it doesn't work:
controller.text.trim().isEmpty()
My code:
TextFormField(
controller: controller,
),
controller.text.trim().isEmpty()
How to continuously get whether the TextField's text is empty in Flutter? I would appreciate any help. Thank you in advance!
full example:
code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController _controller = TextEditingController();
String _text = '';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: Container(
padding: const EdgeInsets.all(16),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(_text),
const SizedBox(height: 20),
TextField(
controller: _controller,
onChanged: (value) {
setState(() {
_text = value;
});
},
decoration: const InputDecoration(
hintText: 'Enter text',
),
),
// submit
ElevatedButton(
onPressed: _text.isEmpty
? null
: () {
setState(() {
_text = _controller.text;
});
},
child: const Text('Submit'),
),
],
),
),
),
);
}
}
It can be done without any temporary variable using ValueListenableBuilder
After some research figured out
controller.text by itself is not listenable
TextEditingController extends ValueNotifier<TextEditingValue> i.e you can use ValueListenableBuilder from material package to listen to text changes.
Code:
class _MyWidgetState extends State<MyWidget> {
late TextEditingController textEditingController;
#override
void initState() {
textEditingController = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
TextField(
controller: textEditingController,
),
ValueListenableBuilder<TextEditingValue>(
valueListenable: textEditingController,
builder: (context, value, child) {
return ElevatedButton(
onPressed: value.text.isNotEmpty ? () {} : null,
child: const Text('I am disabled when text is empty'),
);
},
),
],
),
),
);
}
}
Without text:
With text:
You can add listener to your TextEditingController and call setState to update the UI.
late TextEditingController controller = TextEditingController()..addListener(() {
setState((){}); // to update the ui
});
The place you will use controller.text.trim().isEmpty() will show the updated state.
Example
class Test extends StatefulWidget {
const Test({super.key});
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
late TextEditingController controller = TextEditingController()
..addListener(() {
setState(() {}); // to update the ui
});
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: controller,
),
ElevatedButton(
onPressed: controller.text.trim().isEmpty ? null : () {},
child: Text("Button"))
],
);
}
}

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

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.

Text input value is disappearing Flutter

When I type a text and hit 'ok' or click the back button to hide the keyboard the input's value vanishes
Adding a TextEditingController
class ChatCepBottomSheet extends StatelessWidget {
final TextEditingController _cepController = TextEditingController();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
TextFormField(
controller: _cepController,
),
SizedBox(height: 32),
Button(
buttonText: 'Search',
tapHandler: () {},
)
],
);
}
}
I expected that the typed text staid in the text controller
When keyboard opens it changes the screen behavior and when we close it it re renders the screen and due to that this field initialized again by empty constructor.
TextEditingController _cepController = TextEditingController();
Stateful or stateless it doesnt matter if you are in this kind of issue like keyboard opening etc
try defining final TextEditingController _cepController = TextEditingController(); outside of this class
What you are trying to do will not work with Flutter. You are using a stateless widget and trying to preserve state (data), which is impossible by definition. You need to rewrite your class using a stateful widget - https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html.
See an example below:
import 'package:flutter/material.dart';
class ChatCepBottomSheet extends StatefulWidget {
#override
_ChatCepBottomSheetState createState() => _ChatCepBottomSheetState();
}
class _ChatCepBottomSheetState extends State<ChatCepBottomSheet> {
final TextEditingController _cepController = TextEditingController();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
TextFormField(
controller: _cepController,
),
SizedBox(height: 32),
Button( //choose a button class or custom class
buttonText: 'Search',
tapHandler: () {},
)
],
);
}
}````
The declaration of the "Button" is wrong. Check the Docs for more information. Plus, you can't preserve the state in a StatelessWidget.
This will help:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final TextEditingController _cepController = TextEditingController();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: Text('Demo')),
body: Column(
children: <Widget>[
TextFormField(
controller: _cepController,
),
SizedBox(height: 32),
FlatButton(
child: Text('Search'),
onPressed: () {},
)
],
)
);
}
}
I had a similar problem. Not sure this is the solution, as other answers above have already proposed a solution, but what worked for me was; Removing the texteditingcontroller and using an onChanged instead