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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 08:20:26