I want to use a TextField
in combination with BLoC package in Flutter. The aim is to keep the content of the TextField
in sync with the respective property of the BLoC.
Because BLoC is based on a BlocBuilder
widget that basically subscribes to a Stream
and updates and re-renders all affected child widgets when the Stream
emits something new, I thought the following approach would be sufficient:
BlocBuilder<MyCubit, MyState>(
builder: (BuildContext context, MyState state) {
TextFormField(
initialValue: state.model.title,
onChanged: (String value) => myCubit.setTitle(value),
),
}
);
And actually, it's working. The problems appear when I want to be able to override the value from the BLoC. In my case, I have a TextField
and a select field (DropdownButton
). When the user selects a value from the DropdownButton
, the content of the TextField
is supposed to be overridden.
With the above approach, when I call setTitle(value)
in the onChange
of the DropdownButton
, the value in the Cubit
is overridden, but the TextField
is left unchanged.
Then I had the idea to use a TextEditingController
that handles the text of the TextField
(like it's suggested here by Felix Angelov), updates the Cubit
on change and listens to external changes to the respective value in the Cubit
:
BlocConsumer<MyCubit, MyState>(
listener: (BuildContext context, MyState state) {
if (state is InitialState) {
_controller.addListener(() {
myCubit.setTitle(_controller.text);
});
return;
}
_controller.text = state.title;
},
builder: (BuildContext context, MyState state) {
TextFormField(
controller: _controller,
),
}
);
If I do this, then the initial value is correctly set by the Cubit
, the value is updated in the TextField
when I change it using the DropdownButton
and it's updated in the Cubit when the text in the TextField
changes. However, there is one problem: every time I enter a character inside the TextField
, it loses its focus.
autofocus: true
to the rescue? Unfortunately not. This prevents to lose focus, but then it jumps to the first character of the entered text whenever I enter something. Also, I don't want the TextField
to have focus initially. Setting a FocusNode
also does not help.
Even setting a UniqueKey
like it's suggested here does not change something.
The ideas presented here regarding FocusNode
and onEditingComplete
did not solve my problem either.
Does anyone have a working solution for a TextField in combination with BLoC that fulfills the following requirements:
- When something in the
TextField
is entered, the BLoC/Cubit is updated accordingly - When the BloC/Cubit is updated, the
TextField
's content is updated accordingly - There is no unexpected, annoying keyboard dismiss behavior or
TextField
losing its focus - The
TextField
can have an initial value but does not have to be focussed on render
I could not find a solution despite having researched for quite some time now.
CodePudding user response:
In your later attempt to change TextField
text using TextEditingController
why your TextField
is wrapped inside BlocBuilder
? I believe this would work correctly for updating TextField
's text from your BLoC:
BlocListener<MyCubit, MyState>(
listener: (BuildContext context, MyState state) {
if(state.title != _controller.text) {
_controller.text = state.title;
_controller.selection = TextSelection.collapsed(offset: state.title.length);
}
},
child: TextFormField(
controller: _controller,
),
);
And for updating value inside of your BLoC with events coming from your TextField
, register a listener to the TextEditingController
in your initState
.
@override
void initState() {
super.initState();
_controller.addListener(_changed);
}
@override
void dispose() {
_controller.removeListener(_changed);
super.dispose();
}
_changed() {
myCubit.setTitle(_controller.text);
}
CodePudding user response:
I think that the following code for TextField
is correct, except that you can use a TextEditingController
to set its initial value. You can set the initial value of the controller inside initState
, so you need to use a stateful widget.
@override
void initState() {
super.initState();
final state = BlocProvider.of<MyCubit>(context, listen: false);
_controller = TextEditingController(text: state.model.title);
}
BlocBuilder<MyCubit, MyState>(
builder: (BuildContext context, MyState state) {
TextFormField(
controller: _controller,
onChanged: (String value) => myCubit.setTitle(value),
),
}
);
In the onChanged
of the DropdownButton
, in addition to calling setTitle(value)
on the cubit, change the value of the TextField
using its controller
.
(value){
myCubit.setTitle(value);
_controller.text = value;
}
The only problem that may still be unsolved is when calling _controller.text = value;
the cursor moves to the beginning --before the first character.