【问题标题】:Flutter: setState() or markNeedsBuild() called during build. Using future builder and obxFlutter:在构建期间调用 setState() 或 markNeedsBuild()。使用 future builder 和 obx
【发布时间】:2021-05-11 23:39:31
【问题描述】:

我正在使用颤振和GetX,所以我在我的代码中实现Obx

我有 3 个文件:

questionnaire.dart questionnnaire_controller.dart popup.dart

popup.dart 里面我有弹出窗口的布局。

questionnaire.dart 内,我有代码显示弹出窗口的内容,该弹出窗口显示要回答的问卷。

questionnaire_controller.dart 内部,我使用了一些变量和函数,例如异步获取调查问卷数据的函数getQuestionnaires(),或列表questionnaires,或保留a 实例的变量selectedQuestionnaire已选择的问卷。

popup.dart 中,如果选择了问卷,我必须在弹出对话框的顶部显示问卷的标题。部分代码如下:

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,
            );
          });
    }
  }
}

如您所见,在 obx 中,我得到了 selectedQuestionnaireTitle 的值,这是一个存在于 questionnnaire_controller.dart 中的变量。

questionnaire.dart 内,我有一个future builder,它带来了我的问卷数据,以便用户通过下拉列表选择其中一个,然后单击下一步回答相应的问题。对我们的案例有用的部分代码如下:

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,
                                    )
                                  ],
                                );
                          }
                        }),
                  ),
                );
              } 

您可以在上面的代码中看到一个名为 Dropdown 的小部件。这是我创建的有状态小部件。这个小部件也存在于questionnaire.dart 中。 Dropdown的代码如下。

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(),
    );
  }
}

当我运行代码并打开弹出对话框时,我收到以下错误

在构建 Dropdown(dirty, state: _DropdownState#97b88) 时抛出了以下断言: 在构建期间调用 setState() 或 markNeedsBuild()。

无法将此 Obx 小部件标记为需要构建,因为 框架已经在构建小部件的过程中。一个小部件可以 仅在以下情况之一被标记为需要在构建阶段构建 它的祖先正在建造中。这个例外是允许的,因为 框架在子级之前构建父级小部件,这意味着 肮脏的后代将永远被建造。否则,框架可能 在此构建阶段不要访问此小部件。其上的小部件 setState() 或 markNeedsBuild() 被调用为: Obx 状态: _ObxState#d84a8 发出违规调用时当前正在构建的小部件是:下拉脏状态: _DropdownState#97b88 相关的导致错误的小部件是:Dropdown

我的问题是,我该如何解决这个错误?我知道以下功能可能会有所帮助

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

但是我应该在哪里实现上述功能呢?

感谢您的宝贵时间

【问题讨论】:

  • addPostFrameCallback ---> 会在构建完成后调用,所以在 initState() 方法中初始化这个 addPostFrameCallback 以避免异常
  • 异常是由于调用 addPostFrameCallback 并仅在方法内部调用 setState 引起的

标签: flutter flutter-web flutter-getx


【解决方案1】:

如果您使用 Statefull 小部件,您可以在 initState 中进行初始化

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

需要知道的:

  1. addPostFrameCallback 将在构建调用完成后触发

【讨论】:

  • 感谢您的回答!我在构建函数之前的 _DropdownState(State 类)中添加了这个。看来它正在做这项工作。唯一的事情是,当我打开弹出对话框时,错误会立即显示(仅几毫秒),然后显示小部件。我们该如何解决这个问题?
  • 能否分享一下图片的截图。
猜你喜欢
  • 2020-12-12
  • 2020-12-28
  • 2021-01-06
  • 2021-05-31
  • 2021-08-24
  • 2020-08-11
  • 2020-06-29
相关资源
最近更新 更多