Home > Enterprise >  TextField and BLoC - value not updated or keyboard dismissed
TextField and BLoC - value not updated or keyboard dismissed

Time:10-19

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.

  • Related