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

Flutter购物车异常:无法创建不同变体的Food类新实例

问题根源

你遇到的核心问题是购物车中存储的是原Food对象的引用,而非独立的商品条目。当切换变体并再次添加时,修改的是同一个Food实例的selectedVar属性,导致所有引用该实例的购物车条目同步变更。此外,原逻辑用selectedVars跟踪变体的方式无法区分「商品+变体」的唯一组合,判断逻辑存在漏洞。

解决方案

重构数据结构,将购物车条目与原商品分离,创建独立的CartItem类来封装商品、所选变体和数量,确保每个购物车条目都是独立实例。

1. 创建CartItem类

用于存储购物车中的独立条目,包含原商品、所选变体和数量:

class CartItem {
  final Food food;
  final Variation selectedVariation;
  int quantity;

  CartItem({
    required this.food,
    required this.selectedVariation,
    this.quantity = 1,
  });

  // 重写==和hashCode,用于判断是否为同一商品+变体组合
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is CartItem &&
          runtimeType == other.runtimeType &&
          food.name == other.food.name && // 若商品有唯一ID,建议用ID替代name
          selectedVariation.title == other.selectedVariation.title;

  @override
  int get hashCode => food.name.hashCode ^ selectedVariation.title.hashCode;
}

2. 修改全局购物车列表

替换原userOrders和selectedVars:

// 移除原List<Variation?> selectedVars = [];
List<CartItem> userOrders = [];

3. 调整FoodQuantity的添加逻辑

不再直接添加原Food对象,而是创建CartItem实例,并判断购物车中是否已有相同「商品+变体」的条目:

GestureDetector(
  onTap: () {
    if (widget.food.selectedVar == null) {
      errorDialog(
        context,
        'ERROR',
        'Please select a variation',
      );
    } else {
      final selectedVar = widget.food.selectedVar!;
      // 查找购物车中是否已有相同商品+变体的条目
      final existingItemIndex = userOrders.indexWhere(
        (item) => item.food == widget.food && item.selectedVariation == selectedVar,
      );

      if (existingItemIndex != -1) {
        // 已有条目,增加数量
        context.read<FoodCount>().increment(userOrders[existingItemIndex]);
      } else {
        // 新增条目
        userOrders.add(CartItem(
          food: widget.food,
          selectedVariation: selectedVar,
          quantity: 1,
        ));
      }
      context.read<Counter>().update();
      setState(() {});
    }
  },
  child: const Text(
    '+',
    style: TextStyle(
      fontSize: 30,
      fontWeight: FontWeight.bold,
      color: Colors.white38,
    ),
  ),
)

4. 修改FoodCount Provider

适配CartItem对象,操作购物车条目的数量:

class FoodCount with ChangeNotifier {
  void increment(CartItem item) {
    item.quantity++;
    notifyListeners();
  }

  void decrement(CartItem item) {
    item.quantity--;
    notifyListeners();
  }

  int display(CartItem item) {
    return item.quantity;
  }

  void reset(CartItem item) {
    item.quantity = 0;
    notifyListeners();
  }

  void update() {
    notifyListeners();
  }
}

5. 调整Counter Provider的统计逻辑

适配CartItem列表的数量统计:

class Counter with ChangeNotifier {
  int _count = userOrders.fold(
      0, (previousValue, element) => previousValue + element.quantity);

  int get count => _count;

  void reset() {
    _count = 0;
    notifyListeners();
  }

  int update() {
    _count = userOrders.fold(
        0, (previousValue, element) => previousValue + element.quantity);
    notifyListeners();
    return _count;
  }
}

6. 更新CheckoutView的列表渲染

适配CartItem数据,渲染购物车条目:

itemBuilder: ((context, index) {
  final cartItem = userOrders[index];
  final food = cartItem.food;
  final variation = cartItem.selectedVariation;

  return Container(
    padding: const EdgeInsets.symmetric(horizontal: 5),
    height: 160,
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(20),
    ),
    child: Row(
      children: [
        Stack(
          children: [
            Container(
              padding: const EdgeInsets.all(8),
              width: 130,
              height: 130,
              child: Image.asset(food.imgUrl, fit: BoxFit.fitHeight),
            ),
            Container(
              padding: const EdgeInsets.all(8),
              width: 60,
              height: 60,
              child: Image.asset(variation.imgURL, fit: BoxFit.fitHeight),
            ),
          ],
        ),
        Expanded(
          child: Container(
            padding: const EdgeInsets.only(top: 20, left: 10, right: 10),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Padding(
                      padding: const EdgeInsets.only(top: 25.0),
                      child: Text(
                        food.name,
                        style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                      ),
                    ),
                    IconButton(
                      icon: const Icon(Icons.close),
                      onPressed: () {
                        context.read<FoodCount>().reset(cartItem);
                        userOrders.remove(cartItem);
                        context.read<TotalPrice>().update();
                        context.read<FavFood>().peeQee();
                        context.read<Counter>().update();
                        context.read<FavFood>().update();
                        setState(() {});
                      },
                    )
                  ],
                ),
                Text(
                  variation.title,
                  style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w300, fontStyle: FontStyle.italic),
                ),
                _buildIconText(Icons.access_time_outlined, Colors.blue, food.waitTime),
                Row(
                  children: [
                    Text(
                      getCurrency(),
                      style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
                    ),
                    Text(
                      '${food.price}',
                      style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                    ),
                  ],
                ),
                Padding(
                  padding: const EdgeInsets.only(right: 30),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      GestureDetector(
                        onTap: () {
                          context.read<FoodCount>().decrement(cartItem);
                          context.read<FavFood>().peeQee();
                          context.read<Counter>().update();
                          context.read<FavFood>().update();
                          if (context.read<FoodCount>().display(cartItem) == 0) {
                            userOrders.remove(cartItem);
                          }
                          setState(() {});
                        },
                        child: const Padding(
                          padding: EdgeInsets.only(right: 8.0),
                          child: Text('-', style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold)),
                        ),
                      ),
                      Container(
                        padding: const EdgeInsets.all(12),
                        decoration: const BoxDecoration(shape: BoxShape.circle, color: Color(0xff453658)),
                        child: Text(
                          context.watch<FoodCount>().display(cartItem).toString(),
                          style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white70),
                        ),
                      ),
                      GestureDetector(
                        onTap: () {
                          context.read<FoodCount>().increment(cartItem);
                          context.read<FavFood>().update();
                          context.read<Counter>().update();
                          context.read<FavFood>().peeQee();
                          setState(() {});
                        },
                        child: const Padding(
                          padding: EdgeInsets.only(left: 8),
                          child: Text('+', style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold)),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ],
    ),
  );
}),

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 21:39:57