How is the class variable being updated without calling setstate? - flutter

How am I able to get the updated value of the TextFormField without using setState((){})?
On TextFormField 's onChanged method, I am setting the class variable to the value, but normally we would need setState((){}) for the value to be updated. I am just wondering how is this working?
class ResetPasswordPage extends StatefulWidget {
const ResetPasswordPage({ Key? key }) : super(key: key);
#override
_ResetPasswordPageState createState() => _ResetPasswordPageState();
}
class _ResetPasswordPageState extends State<ResetPasswordPage> {
String _currentPassword = '';
void onChanged(){
print('current Password is: ');
print(_currentPassword); ----------->this is printing the updated value, without setstate?
}
#override
Widget build(BuildContext context) {
return Container(
TextFormField(
onChanged: (val) => {
_currentPassword = val,
onChanged()
},
),
)
}
}

The variable value does change without the need to use setState, setState is only needed to tell Flutter that the UI has to change to reflect changes in your data.
From Flutter docs:
Calling setState notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a build for this State object.

You can create Notifier -> listener, then set the listener to listen for the Notifier,so any update on the Notifier(controller)it will notify the listener(TextFormField) to update itself.
Like this:
final _controller = TextEditingController();
child: TextFormField(
controller: _controller,
decoration: //decoration,
),
when you call this _controller.text it will notify all the listeners of this TextEditingController that needs update, then you get the same result setState((){}) will give you.

Related

How to update textiew text based on another variable in dart

I have the following code, shown below where every time I make changes to a text field, a function gets called, in my case doSomething(). However, I assumed that since I bound the value of the text to a variable, if that variable were to get updated, the textfield text would also update. This is not happening, and my question is, what would be the simplest way to make the textfield update its text every time the corresponding variable changes.
Edit: Just to clarify, I have no problem getting the doSomething() function to update other parts of the code, I am looking for the inverse where an external variable changes the text of the textfield
import 'package:flutter/material.dart';
class DynamicTextField extends StatefulWidget {
const DynamicTextField(
{
required this.value,
Key? key})
: super(key: key);
final double value;
#override
State<DynamicTextField> createState() =>
_DynamicTextFieldState(value);
}
class _DynamicTextFieldState extends State<DynamicTextField> {
_DynamicTextFieldState(this.value);
final double value;
late TextEditingController textChanged;
#override
void initState() {
textChanged = TextEditingController(text: value.toString());
super.initState();
}
#override
Widget build(BuildContext context) {
return TextField(
controller: textChanged,
onChanged: (text) {
doSomething();
},
);
}
}
you can change the TextField value with the textChanged controller you set to that widget.
String stringVariable = "some value";
textChanged.text = stringVariable;
you can then update the state with a normal SetState(() {}), and it should update the value in TextField
Your value is declared as final. Meaning it can only be set once. Try removing the final declaration.
I also see you’re also declaring the value variable twice, instead of referencing the original value variable.
The second declaration should be in the widgets build method & should = widget.value.
this will update the text everytime DynamicTextField get rebuild.
#override
void initState() {
textChanged = TextEditingController(text: widget.value.toString());
super.initState();
}
Depends how you use the DynamicTextField in another class. The life-cycle of StatefullWidget is call the initState once the widget initialize.

Is there any way to generate the same key for the "same" widget in Flutter without having any values to be based on

I have a widget which can have multiple input sections from the same type. When I delete the first child then it behaves weirdly like showing still the old value from that first widget. I figured out I need to use keys for the children but then my UX gets broken. Let me show you some code snippet, please:
class _ParentState extends State<ParentWidget> {
...
#override
Widget build(BuildContext context) {
return Column(
children: stateList
.mapIndexed((index, element) => ChildWidget(key: UniqueKey(), input: element, onChanged: (text) {
setState(() {
stateList[index].text = text;
});
}))
.toList(),
);
}
}
class _ChildState extends State<ChildWidget> {
final ctrl = TextEditingController();
#override
void initState() {
super.initState();
ctrl.text = widget.input.text;
}
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: ctrl,
onChanged: (value) {
widget.onChanged(value);
}
),
...
]
);
}
}
When I wrote the user experience is broken, I was meaning that whenever the user starts to type into the text field then the focus gets loosen character-by-character since the callback is invoked and then generates a new child widget from the state because it has now "different" key... How can I refactor this? I just need a key mechanism which results the same for a child while it is not disposed yet!?
TL;DR
Solution:
remove key
by default, flutter is smart enough to determine which widget is need to rebuild or not. Thats why, key is very rarely used.
ChildWidget(input: element,
onChanged: (text) {
setState(() {
....
When you set UniquieKey() you widget will have different key every time build method called.
which is: everytime you call SetState() your apps will build NEW widget.
if you want to set key, you can use index value
ChildWidget(
key: ValueKey('$index'),
input: element,
onChanged: (text) {
what caused your "user experience broken".
it because, in your onChange function, you call setState() and also you set UniqueKey to your widget. So.. when setState() has called, it will re-execute build method, and since your ChildWidget has Unique key, it will rebuild the widget.
every time user typing, ChildWidget is rebuild. that caused lost focus

Flutter : my custom Switch widget does not respond to state change

I made a custom SwitchTile widget to avoid code duplication, which is based on a StatefulWidget with a boolean value so this is quiet simple :
class SwitchTile extends StatefulWidget
{
final String title;
final bool value;
final ValueChanged<bool> onChanged;
const SwitchTile({Key? key, required this.title, required this.value, required this.onChanged}) : super(key: key);
#override
State<StatefulWidget> createState() => _SwitchTileState();
}
class _SwitchTileState extends State<SwitchTile>
{
late bool value;
#override
void initState()
{
super.initState();
this.value = widget.value;
}
#override
Widget build(BuildContext context)
{
return ListTile(
title: Text(widget.title),
trailing: Switch.adaptive(
value: this.value,
onChanged: (newValue) {
widget.onChanged(newValue);
this.setState(() => this.value = newValue);
}
)
);
}
#override
void dispose()
{
super.dispose();
}
}
Now I want to synchronize some SwitchTile widget with another boolean value, but when the value changes it does not rebuild as I expected. I use flutter_bloc, the bloc/events/states work very well. See what I tried to do :
// See my Widget build() method ; bloc variable is declared above
BlocBuilder<ChadsVascBloc, ChadsVascState>(
builder: (BuildContext context, ChadsVascState state)
{
return Column(
children: [
// this works very well
ListTile(
title: Text("Switch : ageOver65 is ${state.ageOver65.toString()}"),
trailing: Switch.adaptive(
value: state.ageOver65,
onChanged: (value) => bloc.add(AgeOver65Toggled(ageOver65: value))
)
),
// this is does not
SwitchTile(
// the change is well triggered in the title property !!!
title: "SwitchTile : ageOver65 is ${state.ageOver65.toString()}",
value: state.ageOver65, // but this does not change the button's status
onChanged: (value) => bloc.add(AgeOver65Toggled(ageOver65: value))
)
]
);
}
),
SwitchTile(
title: AppLocalizations.of(context)!.ageOver65,
value: bloc.state.ageOver65,
onChanged: (value) => bloc.add(AgeOver65Toggled(ageOver65: value)),
)
When I push the last button, the first one work very well ("base" Switch widget) but the second one (custom SwitchTile widget) does not : the title changes but not the button's position.
What did I do wrong ?
So basically, you use the same event twice, that's why the bloc doesn't recognize any changes because you add the same event even it has the different value.
And i think you use the Bloc in the wrong way. Unlike provider and getx, main objective of Bloc is deliver the context with state. It was never meant to save some object inside it for so long.
For your case,
I assume you are trying to edit some kind of object. Use Bloc event to transfer the related object into another screen, in edit screen, Bloc will listen the state and parsing it to the local variabel that was assigned to the Switch (use BlocConsumer, listener to set state)
if u want to change the value of the variabel, go for it, and confirm it by some trigger like button then add another event to finish the edit
The problem seems to be : how to update properly a custom Switch widget ?
It does not seem to be about Bloc, because when I do that :
BlocBuilder<ChadsVascBloc, ChadsVascState>(
builder: (context, state) => SwitchTile(
title: "AgeOver65 is ${state.ageOver65.toString()}",
value: state.ageOver65,
onChanged: null
),
)
If I trigger a state change, ${state.ageOver65.toString()} changes but not the value of the Switch.
Solved : my error was not using BLoC but with bad state management structure. As a Switch widget does not have to manage its own state, my custom Switch widget should not try to changes its own state.
So it can be a simple StatelessWidget but used inside a parent StatefulWidget, or - I my case - in a BlocBuilder. I finally use a simple context.watch() as the value of my custom widget.

Flutter Newbie: Modifying Textfield value breaks focus on TextField

Go easy. I just started learning Flutter a week ago. I'm coming from ReactJS so I have a decent understanding of state management and lifecycle methods. But I'm completely new to Dart and Flutter and how it handles state.
I am writing a quick WebRTC chat application. I have a TextField I'm using to generate room names. I decided I wanted to make the labelText of the TextField, cycle through some random words, every 5 seconds, while the field is not in focus. If the field comes into focus, I stop cycling the label. I do this so that the field appears to have a pre generated random room name.
I am having trouble editing the TextField. I assume this is an issue with setState or my TextEditingController. I'm used to being able to access an input's value, so controllers are odd to me.
Here is my ChangingTextField:
import 'dart:async';
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
//
class ChangingTextField extends StatefulWidget {
final TextEditingController controller;
ChangingTextField({
Key? key,
required this.controller,
}) : super(key: key);
#override
_ChangingTextFieldState createState() => _ChangingTextFieldState();
}
class _ChangingTextFieldState extends State<ChangingTextField> {
FocusNode _focusNode = FocusNode();
Timer? _timer;
String _roomName = "example.com/";
bool _wasFocused = false;
#override
void initState() {
super.initState();
_focusNode = FocusNode();
_timer = Timer.periodic(Duration(seconds: 5), (Timer t) => _genRoomName());
}
#override
void dispose() {
_timer?.cancel();
_focusNode.dispose();
super.dispose();
}
void _requestFocus(){
if(!_wasFocused){
setState(() {
_timer?.cancel();
_wasFocused = true;
FocusScope.of(context).requestFocus(_focusNode);
});
}
}
void _genRoomName(){
WordPair wp = generateWordPairs().take(1).first;
setState(() => _roomName = "example.com/" + wp.first + "-" + wp.second );
}
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
focusNode: _focusNode,
controller: widget.controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: _wasFocused ? "example.com/" : _roomName,
),
onTap: _requestFocus,
),
);
}
}
The parent widget simply passes a TextEditingController into this widget so that I can listen for changes, and (I assume) gather the value of the TextField at a later point in time.
The listener is defined like this in the parent widget:
#override
void initState() {
roomNameController.addListener(() {
setState(() {});
});
super.initState();
}
However, every time I try to change the value of the TextField, after every character that I type, the focus is broken on the ChangingTextField widget, and I must click again inside the TextField to type my next character. I am assuming this issue is because the listener calls setState in the parent widget.
In React terminology I would refer to this as a re-render. If the parent re-renders, the child goes with it, and so the app loses what knowledge it had of where in the widget tree the user was working. However, I feel that the controller needs to exist in the parent, such that, I can acquire the value of the child when needed (e.g. on a button press). Lifting state up and whatnot.
Can someone explain to me what is going on here?
I found the solution. Listening inside of the widget instead of initializing the listener in the parent component, produces the behavior you would expect.
In short, moving the following code:
#override
void initState() {
roomNameController.addListener(() {
setState(() {});
});
super.initState();
}
into the ChangingTextField widget's initState as opposed to having it in the parent's initState, resolved the problem. Best of all, the controller is still created by the parent, so the controller's text is available in the parent when the submit button is pressed.

TextEditingController vs OnChanged

I am looking for a better explanation on the benefit of TextEditingController over OnChanged event for a TextField.
My understanding is that onChanged's setState notifies all widgets of the change in state variable value. This way any widget (e.g. Text) can simply use the state variable and it will be notified of its changes.
My false hopes were TextEditingController would make it even simpler that I won't even need a state variable. Something like below:
import "package:flutter/material.dart";
class TestForm extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return TestFormState();
}
}
class TestFormState extends State<TestForm> {
//string myStateVariable = "";
final ctrl = TextEditingController();
#override
Widget build(BuildContext context) {
var tf = TextField(
controller: ctrl,
);
var t = Text("Current value: " + ctrl.text); // <<<<<<<<<<< false hope! doesnt work!
var x = Column(children: <Widget>[tf,t],);
return MaterialApp(home: Material(child: Scaffold(
appBar: AppBar(title: Text("Test Form"),),
body: x,
)));
}
}
Can anyone explain why TextEditingController or something similar cannot manage the state itself and notifies all consumers of change in state?
Thanks.
You are just not setting state synchronously that's all. What onChanged does is exactly possible with this approach:
class _TestFormState extends State<TestForm> {
late TextEditingController controller;
#override
void initState() {
controller = TextEditingController()
..addListener(() {
setState(() {});
});
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text('Current Value: ${controller.text}'),
TextField(
controller: controller,
),
],
);
}
}
As you see, we have listener that setting state every time state of the controller changes. This is exactly what onChanged does.
So, about benefits, you can achieve everything with both approach, it's a subjective way.
About benefits:
If you need to hold field values within Stream, onChanged is what you need. In other cases you may use controller.
Actually you won't need both in most of time in my opinion because TextFormField + Form within StatefulWidget is quite complete way to implement form pages. Checkout cookbook: https://flutter.dev/docs/cookbook/forms/validation
TextEditingController actually is managing his own state, that's why you can see the input on the screen once you change it.
You have 2 problems here, the first is that you are not adding any listener to the TextEditingController, you are just asking "give me the current value" only when you build the widget, not "give me the value any time it changes". To achieve this you need to add a listener to the text controller and it will be called every time that the value change.
Try this :
#override
void initState() {
super.initState();
// Start listening to changes.
ctrl.addListener(_printValue);
}
_printValue() {
print("Value: ${ctrl.text}");
}
This will work because print doesn't need to render anything on the screen but if you change it to return a widget it will not work either. That is the second problem, as you pointed out, your parent widget is not been rebuild when the value change, in this case you cannot avoid the setState (or other way to tell flutter that needs to rebuild the widget) when the value change because you need to rebuild the widget to view the change.
Another thing that ill like to point out is that TextEditingController is much powerful and it can be used for more things that just add notifiers to changes. For example if you want a button on other part of the screen that clear the text on a TextField you will need a TextEditingController binded to that field.
Hope it helps!