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)
报错原因
- 构建期间修改Rx状态:在
EmployeesAndCompaniesTab的build方法中直接调用controller.onSearch(),该方法内部执行了RxList.clear()操作,触发Obx组件重建。此时组件正处于构建流程中,框架禁止在构建阶段标记组件需要重建,引发冲突。 - TextField的onChanged参数错误:
onChanged被赋值为controller.onSearch(),这会直接执行方法并传递返回值,而非传递函数引用,导致组件构建时额外触发一次搜索操作。 - 控制器初始化冗余:
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
相关产品推荐
相关产品推荐

