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
相关产品推荐
相关产品推荐

