Firestore保存购物车全量商品异常:文档未创建问题排查
问题描述
开发购物应用时,尝试将购物车商品作为订单详情保存到Firestore,每个商品对应CartItemModel类:
@Parcelize data class CartItemModel( val productId: String, val productImage: String, val productName: String, val freeCoupons: Long, val productPrice: String, val cuttedPrice: String, var productQuantity: Long, val maxQuantity: Long, val stockQuantity: Long, val offersApply: Long, val couponsApplied: Long, val inStock: Boolean, val qtyIDs: ArrayList<String>, var selectedCouponId: String?, var discountedPrice: String?, ) : Parcelable
原placeOrderDetails方法仅能保存单个商品到ORDERS>>>orderId路径,订单内通过cart_summary文档存储汇总信息:
override suspend fun placeOrderDetails( orderID: String, cartItemModelList: ArrayList<CartItemModel>, address: String, fullName: String, pinCode: String, paymentMethod: String ): Flow<Resource<Boolean>> { val result = MutableStateFlow<Resource<Boolean>>(Resource.Ideal()) result.emit(Resource.Loading()) val cartDataManager = CartDataManager() val cartSummary = cartDataManager.calculateTotalAmount(cartItemModelList) cartItemModelList.forEach { val orderDetails = hashMapOf<Any, Any>() orderDetails["ORDER ID"] = orderID orderDetails["PRODUCT ID"] = it.productId.toString() orderDetails["PRODUCT IMAGE"] = it.productImage.toString() orderDetails["PRODUCT NAME"] = it.productName.toString() orderDetails["USER ID"] = firebaseAuth.currentUser?.uid!! orderDetails["PRODUCT QUANTITY"] = it.productQuantity if (it.cuttedPrice.isNotEmpty()) { orderDetails["CUTTED PRICE"] = it.cuttedPrice } orderDetails["PRODUCT PRICE"] = it.productPrice if (!it.selectedCouponId.isNullOrEmpty()) { orderDetails["COUPON ID"] = it.selectedCouponId!! } if (!it.discountedPrice.isNullOrEmpty()) { orderDetails["DISCOUNTED PRICE"] = it.discountedPrice!! } else { orderDetails["DISCOUNTED PRICE"] = "0" } orderDetails["ORDER DATE"] = FieldValue.serverTimestamp() orderDetails["PACKED DATE"] = FieldValue.serverTimestamp() orderDetails["SHIPPED DATE"] = FieldValue.serverTimestamp() orderDetails["DELIVERED DATE"] = FieldValue.serverTimestamp() orderDetails["CANCELLED DATE"] = FieldValue.serverTimestamp() orderDetails["ORDER STATUS"] = "ORDERED" orderDetails["PAYMENT METHOD"] = paymentMethod orderDetails["ADDRESS"] = address orderDetails["FULL NAME"] = fullName orderDetails["PIN CODE"] = pinCode orderDetails["FREE COUPONS"] = it.freeCoupons firestore.collection("ORDERS") .document(orderID) .set(orderDetails) .addOnSuccessListener { val orderOtherDetails = hashMapOf<Any, Any>() orderOtherDetails["Total Items"] = cartSummary.totalItems orderOtherDetails["Total Items price"] = cartSummary.totalItemsPrice orderOtherDetails["Delivery Price"] = cartSummary.deliveryPrice orderOtherDetails["Total Amount"] = cartSummary.totalAmount orderOtherDetails["Saved Amount"] = cartSummary.savedAmount orderOtherDetails["PAYMENT STATUS"] = "not paid" orderOtherDetails["ORDER STATUS"] = "Cancelled" firestore.collection("ORDERS").document(orderID) .collection("OtherDetails") .document("cart_summary").set(orderOtherDetails) .addOnSuccessListener { result.value = Resource.Success(true) }.addOnFailureListener { result.value = Resource.Error(it.message.toString()) } }.addOnFailureListener { ex -> result.value = Resource.Error(ex.message.toString()) } } return result }
为实现全量商品保存,修改后的方法使用批量写入将商品存入ORDERS/orderID/Products集合,同时更新订单汇总到order_summary文档:
override suspend fun placeOrderDetails( orderID: String, cartItemModelList: ArrayList<CartItemModel>, address: String, fullName: String, pinCode: String, paymentMethod: String ): Flow<Resource<Boolean>> { val result = MutableStateFlow<Resource<Boolean>>(Resource.Ideal()) result.emit(Resource.Loading()) val cartDataManager = CartDataManager() val cartSummary = cartDataManager.calculateTotalAmount(cartItemModelList) val orderDetailsList = mutableListOf<HashMap<Any, Any>>() for (cartItem in cartItemModelList) { val orderDetails = hashMapOf<Any, Any>() orderDetails["ORDER ID"] = orderID orderDetails["PRODUCT ID"] = cartItem.productId.toString() orderDetails["PRODUCT IMAGE"] = cartItem.productImage.toString() orderDetails["PRODUCT NAME"] = cartItem.productName.toString() orderDetails["USER ID"] = firebaseAuth.currentUser?.uid!! orderDetails["PRODUCT QUANTITY"] = cartItem.productQuantity if (cartItem.cuttedPrice.isNotEmpty()) { orderDetails["CUTTED PRICE"] = cartItem.cuttedPrice } orderDetails["PRODUCT PRICE"] = cartItem.productPrice if (!cartItem.selectedCouponId.isNullOrEmpty()) { orderDetails["COUPON ID"] = cartItem.selectedCouponId!! } if (!cartItem.discountedPrice.isNullOrEmpty()) { orderDetails["DISCOUNTED PRICE"] = cartItem.discountedPrice!! } else { orderDetails["DISCOUNTED PRICE"] = "0" } orderDetails["ORDER DATE"] = FieldValue.serverTimestamp() orderDetails["PACKED DATE"] = FieldValue.serverTimestamp() orderDetails["SHIPPED DATE"] = FieldValue.serverTimestamp() orderDetails["DELIVERED DATE"] = FieldValue.serverTimestamp() orderDetails["CANCELLED DATE"] = FieldValue.serverTimestamp() orderDetails["ORDER STATUS"] = "ORDERED" orderDetails["PAYMENT METHOD"] = paymentMethod orderDetails["ADDRESS"] = address orderDetails["FULL NAME"] = fullName orderDetails["PIN CODE"] = pinCode orderDetails["FREE COUPONS"] = cartItem.freeCoupons orderDetailsList.add(orderDetails) } val batch = firestore.batch() for (i in orderDetailsList.indices) { val orderDetails = orderDetailsList[i] val documentRef = firestore.collection("ORDERS") .document(orderID).collection("Products") .document("Product$i") batch.set(documentRef, orderDetails) } batch.commit() .addOnSuccessListener { val orderOtherDetails = hashMapOf<Any, Any>() orderOtherDetails["Total Items"] = cartSummary.totalItems orderOtherDetails["Total Items price"] = cartSummary.totalItemsPrice orderOtherDetails["Delivery Price"] = cartSummary.deliveryPrice orderOtherDetails["Total Amount"] = cartSummary.totalAmount orderOtherDetails["Saved Amount"] = cartSummary.savedAmount orderOtherDetails["PAYMENT STATUS"] = "not paid" orderOtherDetails["ORDER STATUS"] = "Cancelled" firestore.collection("ORDERS").document(orderID) .collection("OtherDetails") .document("order_summary") .set(orderOtherDetails) .addOnSuccessListener { result.value = Resource.Success(true) } .addOnFailureListener { ex -> result.value = Resource.Error(ex.message.toString()) } } .addOnFailureListener { ex -> result.value = Resource.Error(ex.message.toString()) } return result }
修改后下单时,每个商品文档均显示提示:"This document does not exist, it will not appear in queries or snapshots",文档未完全创建,请求排查原因。
问题排查与修复
核心原因
- 异步流程未同步:在
suspend函数中使用Firestore的回调API,未挂起等待操作完成,函数提前返回result流,导致批量写入操作被中断,文档未完成创建。 - 父文档未初始化:修改后的代码仅写入子集合
Products和OtherDetails,未创建ORDERS/orderID主文档。Firestore中父文档不存在时,子集合文档会显示“不存在”提示,且无法被正常查询和快照捕获。 - 语法错误:回调中的
ex -是语法错误,应为ex ->,导致异常无法正确捕获。
修复方案(推荐使用挂起函数)
使用Firestore KTX的挂起函数替代回调,确保suspend函数等待所有操作完成,同时初始化订单主文档:
override suspend fun placeOrderDetails( orderID: String, cartItemModelList: ArrayList<CartItemModel>, address: String, fullName: String, pinCode: String, paymentMethod: String ): Flow<Resource<Boolean>> { val result = MutableStateFlow<Resource<Boolean>>(Resource.Ideal()) result.emit(Resource.Loading()) return try { val cartDataManager = CartDataManager() val cartSummary = cartDataManager.calculateTotalAmount(cartItemModelList) val batch = firestore.batch() // 1. 初始化订单主文档,确保父文档存在 val orderMainRef = firestore.collection("ORDERS").document(orderID) batch.set(orderMainRef, hashMapOf( "ORDER ID" to orderID, "USER ID" to firebaseAuth.currentUser?.uid!!, "ORDER DATE" to FieldValue.serverTimestamp(), "ORDER STATUS" to "ORDERED", "PAYMENT METHOD" to paymentMethod, "ADDRESS" to address, "FULL NAME" to fullName, "PIN CODE" to pinCode )) // 2. 批量添加商品到Products子集合 cartItemModelList.forEachIndexed { index, cartItem -> val productDocRef = orderMainRef.collection("Products").document("Product$index") val productDetails = hashMapOf<Any, Any>().apply { this["PRODUCT ID"] = cartItem.productId this["PRODUCT IMAGE"] = cartItem.productImage this["PRODUCT NAME"] = cartItem.productName this["PRODUCT QUANTITY"] = cartItem.productQuantity if (cartItem.cuttedPrice.isNotEmpty()) { this["CUTTED PRICE"] = cartItem.cuttedPrice } this["PRODUCT PRICE"] = cartItem.productPrice cartItem.selectedCouponId?.takeIf { it.isNotEmpty() }?.let { this["COUPON ID"] = it } this["DISCOUNTED PRICE"] = cartItem.discountedPrice ?: "0" this["FREE COUPONS"] = cartItem.freeCoupons this["PACKED DATE"] = FieldValue.serverTimestamp() this["SHIPPED DATE"] = FieldValue.serverTimestamp() this["DELIVERED DATE"] = FieldValue.serverTimestamp() this["CANCELLED DATE"] = FieldValue.serverTimestamp() } batch.set(productDocRef, productDetails) } // 3. 提交批量操作 batch.commit() // 4. 写入订单汇总信息 val summaryDetails = hashMapOf<Any, Any>().apply { this["Total Items"] = cartSummary.totalItems this["Total Items price"] = cartSummary.totalItemsPrice this["Delivery Price"] = cartSummary.deliveryPrice this["Total Amount"] = cartSummary.totalAmount this["Saved Amount"] = cartSummary.savedAmount this["PAYMENT STATUS"] = "not paid" this["ORDER STATUS"] = "Cancelled" } orderMainRef.collection("OtherDetails").document("order_summary").set(summaryDetails) result.emit(Resource.Success(true)) result } catch (ex: Exception) { result.emit(Resource.Error(ex.message.toString())) result } }
关键修改点
- 用Firestore挂起函数替代回调,确保
suspend函数等待所有操作完成。 - 初始化
ORDERS/orderID主文档,解决子集合文档的“不存在”提示问题。 - 简化代码结构,去掉冗余的
orderDetailsList,直接在遍历中构建批量操作。 - 修复语法错误,确保异常能正确捕获。
内容的提问来源于stack exchange,提问作者Dr Mido
相关产品推荐
相关产品推荐

