5

I'm using flutter and GetX, so i'm implementing Obx inside my code.

I have 3 files:

questionnaire.dart questionnnaire_controller.dart popup.dart

Inside the popup.dart I have the layout of the popup.

Inside questionnaire.dart I have the code that displays the content of a popup that displays a questionnaire to be answered.

Inside questionnaire_controller.dart i have some variables and functions that are used, like the function getQuestionnaires() that gets the questionnaires' data asynchronously, or the list questionnaires, or a variable selectedQuestionnaire that keeps the instance of a questionnaire that has been selected.

Inside the popup.dart I have to display on top of the popup dialog, the title of the questionnaire, if a questionnaire has been selected. A part of the code is the following:

static Future<void> showQuestionnaireInput({String title, Widget child, Widget icon}) async {
    bool mobileSize = Get.size.width <= ResponsiveSizingConfig.instance.breakpoints.desktop;
    if (mobileSize) {
      await Get.to(InputScreenWidget(child, title));
    } else {
      await showDialog(
          context: Get.context,
          builder: (context) {
            return AlertDialog(
              titlePadding: EdgeInsets.all(8),
              contentPadding: EdgeInsets.all(8),
              title: Container(
                decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Colors.grey.shade200, width: 2))),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Padding(
                      padding: EdgeInsets.only(left: 4),
                      child: Row(
                        children: [
                          if (icon != null) icon,
                          if (icon != null) SizedBox(width: 4),
                          Text(title),
                          Obx(() {
                            if(questionnaireController.selectedQuestionnaireTitle.value != '')
                              return Text(questionnaireController.selectedQuestionnaireTitle.value);
                            else
                              return Container();
                          }),
                        ],
                      ),
                    ),
                    CloseButton(
                      onPressed: () {
                        Get.back();
                      },
                    )
                  ],
                ),
              ),
              content: child,
            );
          });
    }
  }
}

As you can see, inside the obx i'm getting the value of the selectedQuestionnaireTitle, which is a variable that exists inside questionnnaire_controller.dart.

Inside the questionnaire.dart i have a future builder that bring my questionnaire data, in order for the user to choose one of them via a dropdown and answer the respective questions by clicking next. A part of the code that is useful for our case is the following:

child: Obx(() {
              if (questionnaireController.questionnaireState.value == QuestionnaireController.QUESTIONNAIRE_CHOOSE) {
                return Container(
                  width: screenWide ? Get.size.width * 0.5 : Get.size.width * 1,
                  child: Center(
                    child: FutureBuilder(
                        future: questionnaireController.getQuestionnaires(),
                        builder: (context, snapshot) {
                          questionnaireController.dialIsBuilt.value = true;
                          print(questionnaireController.dialIsBuilt.value);
                          switch (snapshot.connectionState) {
                            case ConnectionState.waiting:
                              questionnaireController.dialIsBuilt.value = false;
                              print(questionnaireController.dialIsBuilt.value);
                              return CircularProgressIndicator();
                            default:
                              if (snapshot.hasData) {
                                print(questionnaireController.dialIsBuilt.value);
                                return Column(
                                  children: [
                                    Text(
                                      'choose_questionnaire'.tr,
                                      textAlign: TextAlign.center,
                                      style: TextStyle(
                                        fontSize: TextSize.TEXT_LARGE,
                                        fontWeight: FontWeight.w600,
                                        color: EnvironmentVariables.mainColor,
                                      ),
                                    ),
                                    SizedBox(
                                      height: 8,
                                    ),
                                    Dropdown(
                                      questionnaires: questionnaireController.questionnaires,
                                      selectedQuestionnaire: questionnaireController.selectedQuestionnaire,
                                    ),
                                    Obx(
                                      () {
                                        if (questionnaireController.buttonDisplay.value == true) {
                                          return Container(
                                            margin: EdgeInsets.all(16),
                                            child: defaultButton(
                                              text: 'next_question'.tr,
                                              onPressed: () {
                                                questionnaireController.answerQuestionnaire();
                                              },
                                            ),
                                          );
                                        } else {
                                          return Container();
                                        }
                                      },
                                    ),
                                  ],
                                );
                              } else
                                return Column(
                                  children: [
                                    Text(
                                      'choose_questionnaire'.tr,
                                      textAlign: TextAlign.center,
                                      style: TextStyle(
                                        fontSize: TextSize.TEXT_LARGE,
                                        fontWeight: FontWeight.w600,
                                        color: EnvironmentVariables.mainColor,
                                      ),
                                    ),
                                    // Text("no_data".tr),
                                    SizedBox(
                                      height: 32,
                                    )
                                  ],
                                );
                          }
                        }),
                  ),
                );
              } 

You can see, inside the code above, a widget named Dropdown. It is a stateful widget that i have created. This widget, also exists inside questionnaire.dart. The code for Dropdown is as follows.

class Dropdown extends StatefulWidget {
  final List questionnaires;
  final Questionnaire selectedQuestionnaire;

  Dropdown({
    this.questionnaires,
    this.selectedQuestionnaire,
  });

  @override
  _DropdownState createState() => _DropdownState(
        questionnaires: questionnaires,
        // dropdownValue: selectedQuestionnaire.title,
      );
}

class _DropdownState extends State<Dropdown> {
  List questionnaires;
  String dropdownValue = questionnaireController.selectedQuestionnaire.title;

  _DropdownState({
    this.questionnaires,
    // this.dropdownValue,
  });

  @override
  Widget build(BuildContext context) {
    questionnaireController.setSelectedQuestionnaire(questionnaireController.selectedQuestionnaire);
    return DropdownButton(
      isExpanded: true,
      value: dropdownValue,
      icon: Icon(Icons.arrow_downward),
      iconSize: 24,
      elevation: 16,
      style: TextStyle(color: EnvironmentVariables.mainColor, fontSize: TextSize.TEXT_SMALL),
      underline: Container(
        height: 1.6,
        color: EnvironmentVariables.mainColor,
      ),
      onChanged: (newValue) {
        widget.questionnaires.forEach((questionnaire) {
          if (questionnaire.title == newValue) {
            questionnaireController.setSelectedQuestionnaire(questionnaire);
            // questionnaireController.selectedQuestionnaire = questionnaire;
          }
        });
        Future.delayed(Duration(seconds: 5), () => setState(() {
          dropdownValue = questionnaireController.selectedQuestionnaire.title;
        }));


        //Show continue button
        questionnaireController.showButton();

        //Used in reminder
      },
      items: widget.questionnaires.map((questionnaire) {
        return DropdownMenuItem(
          value: questionnaire.title,
          child: Text(
            questionnaire.title,
            style: TextStyle(color: EnvironmentVariables.secondaryColor),
          ),
        );
      }).toList(),
    );
  }
}

When i run the code, and i open the popup dialog, i get the following error

The following assertion was thrown building Dropdown(dirty, state: _DropdownState#97b88): setState() or markNeedsBuild() called during build.

This Obx widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase. The widget on which setState() or markNeedsBuild() was called was: Obx state: _ObxState#d84a8 The widget which was currently being built when the offending call was made was: Dropdown dirty state: _DropdownState#97b88 The relevant error-causing widget was: Dropdown

My question is, how do i solve this error? I know that the following function can be helpful

WidgetsBinding.instance.addPostFrameCallback((_) {
  // executes after build
})

But where should i implement the above function?

Thank you for your time

we_mor
  • 478
  • 5
  • 20

2 Answers2

15

If your using the Statefull widget you can initialize inside the initState

initState(){
 super.initState();
  WidgetsBinding.instance.addPostFrameCallback((_) {
  // executes after build
  });
}

Need to known:

  1. addPostFrameCallback will fire after the build call finished it's work
  • Thank you for your answer! I added this inside the _DropdownState (State class) just before the build function. It seems it's doing the job. The only thing is that when i open the popup dialog, the error displays for an instant (just for some milliseconds) and then the widget displays. How can we fix this? – we_mor Feb 08 '21 at 14:24
  • Could you please share the screenshot of the image. – Balasubramani Sundaram Feb 10 '21 at 03:45
0

In my case (not related to Get package) I was seeing the error while using StreamBuilder, I was modifying the state based on data received from the stream. What I needed to do is to delay it from build process with Future.delayed:

Future.delayed(Duration(milliseconds: 100), () {
  setState(() {
    // Update app state
  });
});

It might introduce infinite loop if state keeps updating without some kind of if check.

Be Kind
  • 4,712
  • 1
  • 38
  • 45