My app has (among other things) a TextField
and ElevatedButton
. I’m managing state with Provider
. I want the button color to change from white to blue when the TextField’s content changes.
I have this code in the TextField:
onChanged: (_) {
context.read<Data>().changeButtonColor();
},
This in the ElevatedButton:
style: ElevatedButton.styleFrom(
backgroundColor: context.watch<Data>().buttonColor),
And this in the Provider:
Color buttonColor = Colors.white;
void changeButtonColor() {
buttonColor = Colors.blue;
notifyListeners();
}
The button’s color changes as expected. The problem I have, however, is that the cursor in the TextField
jumps to the beginning of the text as soon as the onChanged
event is triggered. That makes the app unusable.
Why does the cursor do this? And how do I make the code work as desired, i.e., without the TextField
cursor jumping to the beginning of the text?
EDIT: the parent build()
method includes
Note? selNote = context.watch<Data>().selectedNote;
noteTextController.text = selNote.content;
For reference, here’s the complete code for the two widgets:
// Note Text TextField---------------------------------
Padding(
padding: EdgeInsets.only(
top: 15,
bottom: 15,
),
child: TextField(
controller: noteTextController,
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 10,
decoration: InputDecoration(
border: InputBorder.none,
fillColor: Colors.white,
),
onChanged: (_) {
context.read<Data>().changeButtonColor();
},
),
),
// Save Button------------------------------------------
Padding(
padding: EdgeInsets.only(
top: 15,
bottom: 15,
),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: context.watch<Data>().buttonColor),
onPressed: () async {
selNote = context.read<Data>().selectedNote;
var title = noteTitleController.text;
var content = noteTextController.text;
// If this is a new note ... create it
if (selNote == null) {
context.read<Data>().add(title, content);
} else {
// If this is an already existing note ... update it
selNote!.title = title;
selNote!.content = content;
context.read<Data>().update(title: title, content: content);
}
},
child: Text('Save'),
),
),
2
Answers
Try to modify the value of
TextField
‘s Controller text, whenonChanged
get called:We know, its text implicitly get changed but it deserves a try.
Hope it helps.
The problem is that you’re changing the controller’s text inside the build method. Every time your
TextField
calls theonChanged
callback, the value of button color in the provider will be updated so when it’s being watched in the same widget, it will retrigger thebuild
method which causes the controller’s text to be updated, hence the cursor jump.A
TextField
‘s value should be local, and you shouldn’t manually handle thetext
change if the source of the change is from theTextField
itself. Just rely on the internal change from the controller, and if you want to "mirror" the state to a more global state, you can keep theonChanged
callback. You don’t need to read back the value to the controller.More generally, you shouldn’t call the
controller.text
setter in a declarative/reactive manner. It should only be called in an imperative manner.