skip to Main Content

I’m making a dynamic input form using flutter_bloc to manage form states.
I can add and remove the TextFormField.
The problem is inputs keep losing focus immediately after typing a word.
I understand that BlocSelector rebuilds its content when selected state is changed but I do not know how to persist the key for each input since my TextFormFields are dynamic

My code

class AddQuestionScreenBody extends StatelessWidget {
  AddQuestionScreenBody({super.key});
  final GlobalKey _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(right: 15.0, left: 15.0),
      child: Form(
        key: _formKey,
        child: Column(
          children: [
            BlocSelector<AddQuestionBloc, AddQuestionState, List<String>>(
              selector: (state) => state.answerList,
              builder: (context, answerList) {
                return Column(
                  children: [
                    for (int i = 0; i < answerList.length; i++)
                      Padding(
                        padding: const EdgeInsets.only(bottom: 15.0),
                        child: TextFormField(
                          key: UniqueKey(),
                          initialValue: answerList[i],
                          validator: (value) {
                            if (value == null || value.isEmpty) {
                              return "Answer is required";
                            }
                            return null;
                          },
                          onChanged: (value) {
                            context.read<AddQuestionBloc>().add(
                                  OnChangeAnswerElement(
                                    index: i,
                                    value: value,
                                  ),
                                );
                          },
                          decoration: InputDecoration(
                            label: Text('Answer ${i + 1}'),
                            suffixIconColor: Colors.redAccent,
                            suffixIcon: IconButton(
                              onPressed: () {
                                context.read<AddQuestionBloc>().add(
                                    OnRemoveAnswerElement(
                                        i));
                              },
                              icon: const Icon(Icons.remove),
                            ),
                          ),
                        ),
                      ),
                    ElevatedButton(
                      onPressed: () {
                        context.read<AddQuestionBloc>().add(
                              OnAddAnswerElement(),
                            );
                      },
                      child: const Icon(Icons.add),
                    ),
                  ],
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

How can I solve this issue?

2

Answers


  1. Chosen as BEST ANSWER

    I found the root cause, BlocSelector rebuilds widget tree so the TextFormField will lose its key since I'm using Unikey() as the key.
    The solution is to create my own key for each TextFormField so I can persist the key regardless of Widget tree getting rebuilt.

    class AddQuestionScreenBody extends StatelessWidget {
      AddQuestionScreenBody({super.key});
      final GlobalKey _formKey = GlobalKey<FormState>();
    
      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: const EdgeInsets.only(right: 15.0, left: 15.0),
          child: Form(
            key: _formKey,
            child: Column(
              children: [
                BlocSelector<AddQuestionBloc, AddQuestionState, List<String>>(
                  selector: (state) => state.answerList,
                  builder: (context, answerList) {
                    return Column(
                      children: [
                        for (int i = 0; i < answerList.length; i++)
                          Padding(
                            padding: const EdgeInsets.only(bottom: 15.0),
                            child: TextFormField(
                              key: answerList[i].id, // CHANGE HERE
                              initialValue: answerList[i],
                              validator: (value) {
                                if (value == null || value.isEmpty) {
                                  return "Answer is required";
                                }
                                return null;
                              },
                              onChanged: (value) {
                                context.read<AddQuestionBloc>().add(
                                      OnChangeAnswerElement(
                                        index: i,
                                        value: value,
                                      ),
                                    );
                              },
                              decoration: InputDecoration(
                                label: Text('Answer ${i + 1}'),
                                suffixIconColor: Colors.redAccent,
                                suffixIcon: IconButton(
                                  onPressed: () {
                                    context.read<AddQuestionBloc>().add(
                                        OnRemoveAnswerElement(
                                            i));
                                  },
                                  icon: const Icon(Icons.remove),
                                ),
                              ),
                            ),
                          ),
                        ElevatedButton(
                          onPressed: () {
                            context.read<AddQuestionBloc>().add(
                                  OnAddAnswerElement(),
                                );
                          },
                          child: const Icon(Icons.add),
                        ),
                      ],
                    );
                  },
                ),
              ],
            ),
          ),
        );
      }
    }
    

  2. You should provide AddQuestionBloc to see do you rebiuld screen on every change by doing:

    context.read<AddQuestionBloc>().add(
                                      OnChangeAnswerElement(
                                        index: i,
                                        value: value,
                                      ),
                                    );
    

    And as you have TextFormField, may be you should rebuild hole screen on form submition only with onFieldSubmitted callback?

    Also, try to set FocusNode() for each TextFormField.
    So you need defferent keys, focus nodes and may be controllers for each TextFormField. It depends on your requierments.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search