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
Related
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?
Im using a SideBar() widget inside the SideBarWidget() and using it in my main screen SideBarScreen()
but i cant initialize the controller in the Sidebar() widget... How can i fix this
sidebar_Screen
class _SideBarScreenState extends State<SideBarScreen> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Stack(
children: [
ScaffoldScreen(
iconButton: IconButton(
onPressed: () {SideBarWidget().toggle();},
icon: kScaffoldScreenButtonIcon,
),
),
SideBarWidget(),
],
),
);
}
}
sideBarWidget
class SideBarWidget extends StatelessWidget {
SideBarWidget({Key? key}) : super(key: key);
void toggle() {
SideBarState().toggle();
}
late final SideBar sideBarWidget = SideBar();
#override
Widget build(BuildContext context) {
return sideBarWidget;
}
}
SideBar
class SideBar extends StatefulWidget {}
class SideBarState extends State<SideBar> with SingleTickerProviderStateMixin{
late AnimationController controller;
late Animation<Offset> _offsetAnimation;
#override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: const Duration(seconds: 2));
_offsetAnimation = Tween<Offset>(
begin: const Offset(-1.0, 0.0),
end: const Offset(0.0, 0.0),
).animate(CurvedAnimation(parent: controller, curve: Curves.easeOut));
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
void toggle() {
if (controller.isCompleted) {
controller.reverse();
}
else {controller.forward();}
}
#override
Widget build(BuildContext context) {
return SlideTransition();
}
}
This gives the error LateInitializationError: Field 'controller' has not been initialized.
I tried passing the controller as a parameter to the SideBar() but it gives off an exception:
This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
The problem is that you're creating a new instance of SideBar every time you call SideBarWidget().toggle(). Instead, you should be using the same instance of SideBar so that the state is retained.
class SideBarWidget extends StatelessWidget {
SideBarWidget({Key? key}) : super(key: key);
final sideBar = SideBar();
void toggle() {
sideBar.state.toggle();
}
#override
Widget build(BuildContext context) {
return sideBar;
}
}
So now you are using the same instance of SideBar every time and you don't need to pass any parameters.
Note:
To use like this, you also need to change the SideBar class to a StatefulWidget
class SideBar extends StatefulWidget {
#override
_SideBarState createState() => _SideBarState();
}
I have an issue with updating text inside TextFormField when using Provider as state management.
I reduced my problem to an abstract one (I removed all the clutter code) and here how it works:
there is a someValue in AppState
the someValue can be edited via Form->TextFormField
the someValue is to be reflected as a title of the AppBar when typing (onChange)
the someValue can be updated from external source (in the example it is a button that updates it)
when someValue is updated from external source, it MUST be updated in text Form->TextFormField as well
The last one is causing me the problem. Consider the following code:
AppState.dart
import 'package:flutter/foundation.dart';
class AppState extends ChangeNotifier{
String someValue = '';
updateSomeValue(String newValue){
someValue = newValue;
notifyListeners();
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:text_ctrl_issue/app_state.dart';
void main() {
runApp(ChangeNotifierProvider(create: (_) => AppState(), child: 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> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late TextEditingController _controller;
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_controller = TextEditingController();
}
#override
Widget build(BuildContext context) {
final provider = Provider.of<AppState>(context);
// following line of code makes it possible for text to be changed by button
// and reflected in TextFormField
// but it causes nasty side effect, that when typing, cursor always goes to beginning of the line
_controller.text = provider.someValue;
return Scaffold(
appBar: AppBar(
title: Text(provider.someValue),
),
body: Center(
child: Form(
key: _formKey,
child: Column(children: [
TextFormField(
controller: _controller,
onChanged: (value) {
provider.updateSomeValue(value);
},
),
ElevatedButton(
onPressed: () {
provider.updateSomeValue('foo_bar');
},
child: Text('change text external source'))
])),
),
);
}
}
The problem:
When I added the line _controller.text = provider.someValue; it fixed the issue of updating TextFormField when button is clicked, but it create new issue, that when typing in TextFormField, it is also triggered, cause carret of text field to move to the beginning of the text field.
How to make it work so the text (value) of a TextFormField can be updated externally, without causing carret issue when typing?
EDIT
The answer of Yeasin Sheikh using addListener doesn't quite work (it is hacky) because:
it listens to every event (e.g. onFocus or cursor changed)
it does not take into account situation that EleveatedButton is in different scope than _controller (e.g. is in different widget).
An easy way of doing this by listening TextEditingController, while the TextFormField is the ruler here.
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late TextEditingController _controller;
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_controller = TextEditingController()
..addListener(() {
Provider.of<AppState>(context, listen: false)
.updateSomeValue(_controller.text);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(context.watch<AppState>().someValue),
),
body: Center(
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _controller,
),
ElevatedButton(
onPressed: () {
_controller.text = 'foo_bar';
},
child: Text('change text external source'))
],
),
),
),
);
}
}
Also, you can check riverpod
import 'package:flutter/foundation.dart';
class AppState extends ChangeNotifier
{
TextEditingController _controller=TextEditingController();
TextEditingController get controller=>_controller();
String someValue = '';
updateSomeValue(String newValue)
{
someValue = newValue;
notifyListeners();
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:text_ctrl_issue/app_state.dart';
void main() {
runApp(ChangeNotifierProvider(create: (_) => AppState(), child: 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> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
#override
void dispose() {
super.dispose();
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
final provider = Provider.of<AppState>(context);
return Scaffold(
appBar: AppBar(
title: Text(provider.someValue),
),
body: Center(
child: Form(
key: _formKey,
child: Column(children: [
TextFormField(
controller: Provider.controller,
onChanged: (v) {
provider.updateSomeValue(v);
},
),
ElevatedButton(
onPressed: () {
provider.updateSomeValue('foo_bar');
},
child: Text('change text external source'))
])),
),
);
}
}
According to the docs, didUpdateWidget() is
Called whenever the widget configuration changes.
I took this to mean that didUpdateWidget() will be triggered if a widget is being rebuilt with an argument that is different than the argument it was given in the previous build. But after a simple test I seem to stand corrected:
Example :
class A extends StatefulWidget {
const A({Key key}) : super(key: key);
#override
State<A> createState() => _AState();
}
class _AState extends State<A> {
#override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTapDown: (_) => setState(() {}),
child: Container(
width: 300,
height: 300,
color: Colors.blue,
child: B(6),
),
),
);
}
}
class B extends StatefulWidget {
final int x;
const B(this.x, {Key key}) : super(key: key);
#override
_BState createState() => _BState();
}
class _BState extends State<B> {
#override
void didUpdateWidget(covariant B oldWidget) {
print('[${DateTime.now()}] widget has changed!');
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) => Container();
}
A has a child B that it rebuilds after every tap down gesture, and passes to it the int 6. Even though the argument remains the same, didUpdateWidget() gets invoked on every tap down.
On the other hand, if instead of 'B(6)' I give as an argument 'const B(6)', then didUpdateWidget() does not fire.
So am I to understand that an instance of a widget in and of itself is considered a configuration? Two instances of the same widget class with the exact same arguments are considered different configurations?
Is it possible that the last String from a list can be input automatically to a TextField?
The list is empty for first few seconds and it is changing. It is also initialize after build context.
If yes please provide a code because I am new to flutter.
I know how to change the text from a textfield by using TextEditingController.
final TextEditingController _controller = TextEditingController();
TextField(controller: _controller)
ElevatedButton(
onPressed: () {
const newText = 'Hello World';
final updatedText = _controller.text + newText;
_controller.value = _controller.value.copyWith(
text: updatedText,
selection: TextSelection.collapsed(offset: updatedText.length),
);
},
)
but it has a button, how can I automate this?
Yes you can do this by TextEditingController... Let's have a code example where you have a List of String.... e.g
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late TextEditingController _textEditingController;
final List<String> _exampleList = [];
#override
void initState() {
Future.delayed(
const Duration(seconds: 2),
() {
if (mounted) {
setState(() {
_exampleList.add("apple");
_textEditingController = TextEditingController(text: _exampleList.last);
});
}
},
);
_textEditingController = TextEditingController(text: "Loading ...");
super.initState();
}
#override
void dispose() {
_textEditingController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: TextFormField(
enabled: _exampleList.isNotEmpty,
controller: _textEditingController,
),
),
),
);
}
}
here is the result... this is just a simple explanation of how you can show last item of list in textfield