You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter+GetX求助:build期间调用setState/markNeedsBuild异常

解决Flutter GetX中setState() or markNeedsBuild() called during build异常

问题场景

开发Flutter移动端应用,使用GetX作为状态管理工具,构建EmployeesAndCompaniesTab组件时抛出如下异常:

构建EmployeesAndCompaniesTab(dirty)时抛出以下断言:setState() or markNeedsBuild() called during build.
此Obx组件无法被标记为需要构建,因为框架已处于组件构建过程中。

调用栈关键指向:

#12     EmployeesAndCompaniesController.onSearch (package:/employees_and_companies_controller.dart:140:49)
#13     EmployeesAndCompaniesTab.build (package:/employees_and_companies_tab.dart:31:16)

报错原因

  1. 构建期间修改Rx状态:在EmployeesAndCompaniesTab的build方法中直接调用controller.onSearch(),该方法内部执行了RxList.clear()操作,触发Obx组件重建。此时组件正处于构建流程中,框架禁止在构建阶段标记组件需要重建,引发冲突。
  2. TextField的onChanged参数错误:onChanged被赋值为controller.onSearch(),这会直接执行方法并传递返回值,而非传递函数引用,导致组件构建时额外触发一次搜索操作。
  3. 控制器初始化冗余:EmployeesAndCompaniesTab继承自GetWidget<EmployeesAndCompaniesController>,无需手动调用Get.put,GetWidget会自动注入已注册的控制器实例。

解决方案

1. 延迟执行状态修改操作

避免在build方法中直接调用修改Rx变量的方法,改用以下方式:

  • 使用WidgetsBinding.instance.addPostFrameCallback,在当前构建帧完成后执行搜索
  • 或在控制器的onInit钩子中初始化搜索逻辑

2. 修复TextField的onChanged参数

onChanged需要接收Function(String)类型的函数引用,若onSearch需要搜索文本,需调整方法参数并传递对应函数。

3. 移除冗余的控制器初始化

删除手动Get.put的代码,利用GetWidget的自动注入特性。

代码修改示例

修改后的EmployeesAndCompaniesTab组件

class EmployeesAndCompaniesTab extends GetWidget<EmployeesAndCompaniesController> {
  var companies = <Company>[];
  String query = '';
  
  EmployeesAndCompaniesTab({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 延迟执行搜索,避开组件构建阶段
    WidgetsBinding.instance.addPostFrameCallback((_) {
      controller.onSearch();
    });

    final styleActive = TextStyle(color: Colors.black);
    final styleHint = TextStyle(color: Colors.black54);
    final style = controller.searchController.value.text.isEmpty
        ? styleHint
        : styleActive;

    return SingleChildScrollView(
      child: Column(
        children: [
          Align(
              alignment: Alignment.center,
              child: Padding(
                  padding: EdgeInsets.only(
                      left: getHorizontalSize(40.00),
                      top: getHorizontalSize(10.00),
                      bottom: getHorizontalSize(20.00)),
                  child: Center(
                    child: TabChip(
                        onCheck: () => controller.onSearch(),
                        forceDisableCompanyChip:
                            controller.forceDisableCompanyChip.value),
                  ))),

          // 修复onChanged参数传递
          Container(
            height: 60,
            margin: const EdgeInsets.fromLTRB(16, 16, 16, 5),
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(16),
              color: Colors.white,
              border: Border.all(color: Colors.black26),
              boxShadow: [
                BoxShadow(
                  color: Colors.grey.withOpacity(0.5),
                  spreadRadius: 5,
                  blurRadius: 7,
                  offset: Offset(0, 3),
                ),
              ],
            ),
            padding: const EdgeInsets.symmetric(horizontal: 10),
            child: TextField(
              controller: controller.searchController,
              decoration: InputDecoration(
                icon: Icon(Icons.search, color: style.color),
                suffixIcon: controller.searchController.value.text.isNotEmpty
                    ? GestureDetector(
                        child: Icon(Icons.close, color: style.color),
                        onTap: () {
                          controller.searchController.clear();
                          controller.onSearch();
                          FocusScope.of(context).requestFocus(FocusNode());
                        },
                      )
                    : null,
                hintText: "Rechercher",
                hintStyle: style,
                border: InputBorder.none,
              ),
              style: style,
              // 传递函数引用,若需接收搜索文本则改为 (text) => controller.onSearch(text)
              onChanged: (_) => controller.onSearch(),
            ),
          ),

          Obx(() => controller.searchedCompanyName.value.isNotEmpty
              ? Padding(
                  padding: EdgeInsets.only(
                    left: getHorizontalSize(15.00),
                  ),
                  child: Align(
                      alignment: Alignment.topLeft,
                      child: InputChip(
                        onPressed: () {},
                        onDeleted: () {
                          controller.searchedCompanyName.value = "";
                          controller.searchedCompanyId = -1;
                          controller.onSearch();
                        },
                        avatar: const Icon(
                          Icons.filter,
                          size: 20,
                          color: Colors.black54,
                        ),
                        deleteIconColor: Colors.black54,
                        label: Text("Société : " +
                            controller.searchedCompanyName.value),
                      )))
              : SizedBox.shrink()),

          Obx(() => ListView.builder(
              physics: BouncingScrollPhysics(),
              shrinkWrap: true,
              itemCount:
                  controller.companyModelObj.value.companyItemList.length,
              itemBuilder: (context, index) {
                CompanyItemModel model =
                    controller.companyModelObj.value.companyItemList[index];
                return CompanyItemWidget(
                  model,
                  onTapTxtAccder: () => onTapCompanyDetailBtn(context, model),
                );
              })),

          Obx(() {
            final employeeList = controller.employeeModelObj.value.employeeItemList;
            if (employeeList == null || employeeList.isEmpty) {
              return Center(child: Text("Empty"));
            }
            return ListView.builder(
                physics: BouncingScrollPhysics(),
                shrinkWrap: true,
                itemCount: employeeList.length,
                itemBuilder: (context, index) {
                  EmployeeItemModel model = employeeList[index];
                  return EmployeeItemWidget(
                    model,
                    onTapTxtAccder: () => onTapScanEmployeeBtn(context, model),
                  );
                });
          })
        ],
      ),
    );
  }
}

额外优化:控制器内初始化搜索

若希望控制器初始化时自动执行搜索,可在EmployeesAndCompaniesController中添加onInit钩子:

class EmployeesAndCompaniesController extends GetxController {
  @override
  void onInit() {
    super.onInit();
    onSearch();
  }

  // ...其他业务方法
}

添加后可移除组件中addPostFrameCallback的代码。

内容的提问来源于stack exchange,提问作者cyrille gate

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 22:55:17