Flutter+Firebase+GetX购物车:如何避免重复条目并增加商品数量?
购物车重复条目问题排查与修复
我用GetX和Firebase开发购物车功能,期望用户点击「加入购物车」时,不生成重复条目,只增加对应商品数量,但现在出现了重复条目。
CartController代码
class CartController extends GetxController { static CartController instance = Get.find(); //cart controller for the shopping cart Rx<double> totalCartPrice = 0.0.obs; RxList<CartItemModel> products = RxList<CartItemModel>([]); @override void onReady() { super.onReady(); ever(logController.userModel, editTotalPrice); } void addToCart(ProductModel product) { try { if (_isItemAlreadyAdded(product)) { Get.snackbar("Check your cart", "${product.name} is already added"); } else { String itemID = Uuid().v1(); logController.updateUserData({ "cart": FieldValue.arrayUnion([ { "id": itemID, "productId": product.id, // "name": product.name, "quantity": 1, "price": product.price, "image": product.image, "cost": product.price, } ]) }); Get.snackbar("Item added", "${product.name} was added to your cart"); } } catch (e) { Get.snackbar("Whoops...Cannot add this right now!", "There was an error adding to cart", duration: Duration(seconds: 3), backgroundColor: Colors.pinkAccent); debugPrint(e.toString()); } } void deductCartItem(CartItemModel cartItem) { try { logController.updateUserData({ "cart": FieldValue.arrayRemove([cartItem.toJson()]) }); } catch (e) { Get.snackbar("Error", "Cannot remove this item"); debugPrint(e.toString()); } } editTotalPrice(LoginUserModel usrModel) { if (usrModel.cart!.isEmpty) { print("Cart empty!"); } else if (usrModel.cart!.isNotEmpty) { totalCartPrice.value = 50; print("hi"); usrModel.cart!.forEach((cartItem) { totalCartPrice.value += cartItem.cost!; }); } } bool _isItemAlreadyAdded(ProductModel product) => logController.userModel.value.cart! .where((item) => item.name == product.name) .isNotEmpty; void decreaseQuantity(CartItemModel item) { if (item.quantity == 1) { deductCartItem(item); } else { deductCartItem(item); item.quantity! - 1; logController.updateUserData({ "cart": FieldValue.arrayUnion([item.toJson()]) }); } } void increaseQuantity(CartItemModel item) { deductCartItem(item); item.quantity! + 1; logger.i({"quantity": item.quantity}); logController.updateUserData({ "cart": FieldValue.arrayUnion([item.toJson()]) }); } }
CartItemModel模型类
class CartItemModel { CartItemModel( {this.productId, this.id, this.image, this.name, this.quantity, required this.price, this.cost}); CartItemModel.fromMap(Map<String, dynamic> data) { id = data[ID]; image = data[IMAGE]; name = data[NAME]; quantity = data[QUANTITY]; cost = data[COST].toDouble(); productId = data[PRODUCT_ID]; price = data[PRICE]; } static const COST = "cost"; static const ID = "id"; static const IMAGE = "image"; static const NAME = "name"; static const PRICE = "price"; static const PRODUCT_ID = "productId"; static const QUANTITY = "quantity"; double? cost; String? id; String? image; String? name; double? price; String? productId; int? quantity; Map toJson() => { ID: id, PRODUCT_ID: productId, IMAGE: image, NAME: name, QUANTITY: quantity, COST: price! * quantity!, PRICE: price }; }
LoginUserModel登录相关模型类
class LoginUserModel { String? displayName; String? email; String? photoUrl; String? uid; List<CartItemModel>? cart; LoginUserModel( {this.displayName, this.email, this.photoUrl, this.uid, this.cart}); LoginUserModel.fromSnapshot(DocumentSnapshot<Map<String, dynamic>> snapshot) { displayName = snapshot.data()!["DISPLAYNAME"]; photoUrl = snapshot.data()!["PHOTOURL"]; email = snapshot.data()!["EMAIL"]; uid = snapshot.data()!["UID"]; cart = _convertCartItems(snapshot.data()!["CART"] ?? []); } List<CartItemModel> _convertCartItems(List cartFomDb) { List<CartItemModel> _result = []; if (cartFomDb.length > 0) { cartFomDb.forEach((element) { _result.add(CartItemModel.fromMap(element)); }); } return _result; } List cartItemsToJson() => cart!.map((item) => item.toJson()).toList(); }
LoginController登录控制器代码
class LoginController extends GetxController { static LoginController instance = Get.find(); Rxn<User> fbUser = Rxn<User>(); final googleSignIn = GoogleSignIn(); RxBool isLoggedIn = false.obs; Rx<LoginUserModel> userModel = LoginUserModel().obs; String usersCollection = "users"; GoogleSignInAccount? _googleAcc; LoginUserModel? _userModel; @override void onReady() { super.onReady(); fbUser = Rxn<User>(auth.currentUser); fbUser.bindStream(auth.userChanges()); ever(fbUser, setInitialScreen); } LoginUserModel? get loggedInUserModel => _userModel; setInitialScreen(User? user) { if (user == null) { print("going to login page..."); Get.offAll(() => LoginPage()); } else { print("The user is ${user.displayName}"); userModel.bindStream(listenToUser()); Get.offAll(() => AppSetup()); } } void googleLogin() async { final googleUser = await googleSignIn.signIn(); if (googleUser == null) return; _googleAcc = googleUser; final googleAuth = await googleUser.authentication; final cred = GoogleAuthProvider.credential( accessToken: googleAuth.accessToken, idToken: googleAuth.idToken, ); try { await auth.signInWithCredential(cred).then((res) async { print('Signed in successfully as ' + res.user!.displayName.toString()); print('email: ' + res.user!.email.toString()); LoginUserModel _newUser = LoginUserModel( uid: res.user!.uid, email: res.user!.email!, displayName: res.user!.displayName, photoUrl: res.user!.photoURL, cart: [], ); _addUserToFB(_newUser, res.user!); }); } catch (e) { debugPrint(e.toString()); Get.snackbar("Sign In Failed", "Try again"); } } void signOut() async { googleSignIn.signOut(); auth.signOut(); } updateUserData(Map<String, dynamic> data) { logger.i("UPDATED"); firebaseFirestore .collection(usersCollection) .doc(fbUser.value?.uid) .update(data); } Stream<LoginUserModel> listenToUser() => firebaseFirestore .collection(usersCollection) .doc(fbUser.value!.uid) .snapshots() .map((snapshot) => LoginUserModel.fromSnapshot(snapshot)); _addUserToFB(LoginUserModel usr, User firebaseUser) { firebaseFirestore.collection(usersCollection).doc(usr.uid).set({ "displayName": usr.displayName, "id": usr.uid, "photoURL": usr.photoUrl, "email": usr.email, "cart": usr.cart }); } }
Firebase数据库结构截图

问题根源与修复方案
1. 重复判断逻辑失效
- 原代码用商品名称判断重复,但
addToCart中注释了name字段的写入,导致Firebase中无name值,判断永远不成立,每次都会新增条目。 - 修复:改用唯一标识
productId判断重复,并取消name字段注释:
bool _isItemAlreadyAdded(ProductModel product) => logController.userModel.value.cart! .where((item) => item.productId == product.id) .isNotEmpty; // addToCart中取消注释 "name": product.name,
2. 数量增减逻辑无效果
- 原代码中
item.quantity! + 1仅做计算未赋值,导致数量未更新。 - 修复:给
item.quantity赋值:
void decreaseQuantity(CartItemModel item) { if (item.quantity == 1) { deductCartItem(item); } else { deductCartItem(item); item.quantity = item.quantity! - 1; logController.updateUserData({ "cart": FieldValue.arrayUnion([item.toJson()]) }); } } void increaseQuantity(CartItemModel item) { deductCartItem(item); item.quantity = item.quantity! + 1; logger.i({"quantity": item.quantity}); logController.updateUserData({ "cart": FieldValue.arrayUnion([item.toJson()]) }); }
3. 优化加入购物车逻辑:已存在则直接加数量
原逻辑仅提示已添加,未实现数量递增,修改addToCart:
void addToCart(ProductModel product) { try { final existingItem = logController.userModel.value.cart! .firstWhereOrNull((item) => item.productId == product.id); if (existingItem != null) { deductCartItem(existingItem); existingItem.quantity = existingItem.quantity! + 1; logController.updateUserData({ "cart": FieldValue.arrayUnion([existingItem.toJson()]) }); Get.snackbar("数量更新", "${product.name} 数量已增加"); } else { String itemID = Uuid().v1(); logController.updateUserData({ "cart": FieldValue.arrayUnion([ { "id": itemID, "productId": product.id, "name": product.name, "quantity": 1, "price": product.price, "image": product.image, "cost": product.price, } ]) }); Get.snackbar("商品已添加", "${product.name} 已加入购物车"); } } catch (e) { Get.snackbar("添加失败", "当前无法添加该商品", duration: Duration(seconds: 3), backgroundColor: Colors.pinkAccent); debugPrint(e.toString()); } }
注:需在pubspec.yaml添加collection: ^1.17.2以使用firstWhereOrNull。
4. 修正Firestore字段名大小写不匹配问题
LoginUserModel.fromSnapshot中使用大写字段名,但LoginController._addUserToFB中用小写,导致数据读取失败:
// LoginUserModel.fromSnapshot修改为: displayName = snapshot.data()!["displayName"]; photoUrl = snapshot.data()!["photoURL"]; email = snapshot.data()!["email"]; uid = snapshot.data()!["id"]; cart = _convertCartItems(snapshot.data()!["cart"] ?? []);
5. 修复总价计算初始值错误
原代码初始值设为50,改为从0开始:
void editTotalPrice(LoginUserModel usrModel) { if (usrModel.cart!.isEmpty) { totalCartPrice.value = 0.0; print("购物车为空!"); } else { totalCartPrice.value = 0.0; usrModel.cart!.forEach((cartItem) { totalCartPrice.value += cartItem.cost!; }); } }
内容的提问来源于stack exchange,提问作者Julia Pak
相关产品推荐
相关产品推荐

