Flutter报错:Build期间调用setState/markNeedsBuild(关联Obx)求解决
问题
开发服装商品详情页时,点击加号按钮可同步更新顶部购物车按钮的数值,但返回上一页后再次点击商品卡片,会触发如下错误:
FLUTTER ERROR: setState() or markNeedsBuild() called during build... The widget on which setState() or markNeedsBuild() was called was: Obx
应用功能正常无崩溃,但报错持续存在。
相关代码
详情页代码
import 'package:zeus_app/detailedPage/addtocartbutton.dart'; import 'package:zeus_app/detailedPage/cartcontrol.dart'; import 'package:zeus_app/detailedPage/colors.dart'; import 'package:zeus_app/detailedPage/size.dart'; import 'package:zeus_app/manClothes/sominam.dart'; import 'package:badges/badges.dart'; import 'package:get/get.dart'; import 'package:zeus_app/product/product.dart'; class DetailProductCart extends StatefulWidget { final SoMiNam manSoMi; const DetailProductCart({super.key, required this.manSoMi}); @override _DetailProductState createState() => _DetailProductState(); } class _DetailProductState extends State<DetailProductCart> { CartController cartController = Get.find(); @override Widget build(BuildContext context) { // 问题根源:build过程中修改Observable变量 cartController.initQuantity(); var size = MediaQuery.of(context).size; return Scaffold( backgroundColor: Colors.white, body: ListView( padding: EdgeInsets.zero, children: <Widget>[ Stack( children: [ Container( width: size.width, height: 500, decoration: BoxDecoration( image: DecorationImage( image: AssetImage(widget.manSoMi.image), fit: BoxFit.cover)), ), Padding( padding: const EdgeInsets.only(top: 25, left: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( onPressed: () { Navigator.of(context).pushNamed('/man'); }, icon: const Icon( Icons.arrow_back_ios_new_outlined, color: textColorTabs, ), ), Padding( padding: const EdgeInsets.symmetric( horizontal: 25, vertical: 10), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ Badge( badgeContent: const Text("0"), child: const Icon( Icons.favorite_border_outlined, color: textColorTabs, size: 30, ), ), const SizedBox(width: 20), InkWell( child: Badge( // 报错关联的Obx组件 badgeContent: Obx(() => Text( cartController.numOfItem.value.toString())), child: const Icon( Icons.local_grocery_store_outlined, color: textColorTabs, size: 30, ), ), ), ]), ), ], ), ), ], ), const SizedBox(height: 15), Padding( padding: const EdgeInsets.only(left: 12, right: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ SizedBox( width: 200, child: Text( widget.manSoMi.title.toUpperCase(), style: GoogleFonts.robotoMono( textStyle: const TextStyle( fontWeight: FontWeight.bold, color: Colors.black, height: 1.5, fontSize: 22), ), ), ), Text( widget.manSoMi.price, style: GoogleFonts.robotoMono( textStyle: const TextStyle( fontWeight: FontWeight.bold, color: Colors.black, height: 1.5, fontSize: 18), ), ), ]), ), const SizedBox(height: 20), const ColorsProducts(), const SizedBox(height: 20), const SizeProducts(), const SizedBox(height: 20), ProductCounterAndFavIcon(manSoMi: widget.manSoMi), const SizedBox(height: 25), AddToCart(soMi: widget.manSoMi), ], ), ); } }
CartController代码
import 'package:get/get.dart'; import 'package:zeus_app/manClothes/sominam.dart'; class CartController extends GetxController { var numOfItem = 1.obs; var total = 0.obs; var cartItems = <SoMiNam>[].obs; void removeItem() { if (numOfItem.value > 1) { numOfItem.value--; } } void addItem() { numOfItem.value++; } void addItemInCart(SoMiNam aoSoMi) { cartItems.add(aoSoMi); total.value = total.value + numOfItem.value; numOfItem.value = 1; } void initQuantity() { // 修改Observable变量,触发Obx更新 numOfItem.value = 1; } }
解决方案
错误原因
在build方法中直接调用cartController.initQuantity(),该方法会修改numOfItem这个Observable变量。此时页面正在构建,而Obx组件正监听numOfItem的变化,导致在构建过程中触发组件刷新,违反了Flutter的构建规则(构建期间不能修改状态)。
修复方案
将initQuantity的调用移到initState生命周期方法中,或者使用WidgetsBinding的addPostFrameCallback延迟执行,确保状态修改在页面构建完成后进行。
方案1:使用initState
修改详情页的State类:
class _DetailProductState extends State<DetailProductCart> { CartController cartController = Get.find(); @override void initState() { super.initState(); // 在初始化阶段重置数量,此时页面尚未开始构建 cartController.initQuantity(); } @override Widget build(BuildContext context) { var size = MediaQuery.of(context).size; // 移除build中的cartController.initQuantity()调用 return Scaffold( // ... 原有代码不变 ); } }
方案2:使用addPostFrameCallback(适用于需要在首次构建后执行的场景)
如果initQuantity需要依赖页面构建后的某些数据,可以用延迟执行:
class _DetailProductState extends State<DetailProductCart> { CartController cartController = Get.find(); @override void initState() { super.initState(); // 等待当前帧构建完成后执行 WidgetsBinding.instance.addPostFrameCallback((_) { cartController.initQuantity(); }); } @override Widget build(BuildContext context) { var size = MediaQuery.of(context).size; return Scaffold( // ... 原有代码不变 ); } }
额外优化建议
- 若
numOfItem是当前商品的临时数量,建议不要放在全局的CartController中,而是放在详情页的State里,避免不同商品页面之间互相干扰。 - 全局的
CartController应只维护购物车的总数量和商品列表,当前商品的选择数量属于页面级状态,单独管理更合理。
内容的提问来源于stack exchange,提问作者destructor
相关产品推荐
相关产品推荐

