如何将Firestore的DocumentSnapshot获取数据转换为double类型?
报错原因定位
- 类型转换逻辑错误:你调用
double.parse(documentSnapshot['price'])时,Firestore中存储的price字段本身就是double数值类型,而double.parse()方法仅支持传入字符串类型入参,直接传入double值就会触发type 'double' is not a subtype of type 'String'报错。 - 计算位置错误:你把价格累加逻辑写在
ListView.builder的itemBuilder回调中是完全错误的。itemBuilder是列表滚动时逐帧执行的渲染函数,会随滚动反复触发,放在这里做累加会出现数值重复计算、滚动时价格乱跳的问题;同时你当前代码在每个商品条目处显示累加过程中的临时值,既不是单个商品的单价,也不是最终的购物车总价,逻辑不符合业务预期。
正确实现方案
1. 安全类型转换方法
写一个通用的取值方法,兼容Firestore可能返回的num、int、double、字符串多种格式,避免类型异常崩溃:
double parsePrice(dynamic rawPrice) { // 如果本身就是数值类型,直接转double返回 if (rawPrice is num) return rawPrice.toDouble(); // 如果是字符串类型,尝试解析为double,解析失败返回0 if (rawPrice is String) return double.tryParse(rawPrice) ?? 0.0; // 其他异常情况默认返回0 return 0.0; }
2. 逻辑调整规则
- 总价计算逻辑挪到列表渲染前执行:拿到cart集合的全量文档后,先遍历计算总价,再传入ListView渲染,禁止在itemBuilder中做累加操作。
- 区分单价和总价展示:每个商品条目处显示当前商品自身的单价,不要显示累加过程中的临时值;总价可以在页面底部固定区域单独展示。
修复后的完整代码
Widget bodySection(BuildContext context) { return StreamBuilder( stream: FirebaseFirestore.instance .collection('users') .doc(userid) .collection('cart') .snapshots(), builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) { if (streamSnapshot.hasData) { final cartDocs = streamSnapshot.data!.docs; // 提前计算总价,不要在itemBuilder里累加 double totalPrice = 0; for (var doc in cartDocs) { totalPrice += parsePrice(doc['price']); } return Padding( padding: EdgeInsets.symmetric( horizontal: getProportionateScreenWidth(20)), child: Column( children: [ // 商品列表区域 Expanded( child: ListView.builder( itemCount: cartDocs.length, itemBuilder: (context, index) { final DocumentSnapshot documentSnapshot = cartDocs[index]; // 这里只取当前商品的单价,用于条目展示 final singleItemPrice = parsePrice(documentSnapshot['price']); String imgurl = documentSnapshot['imgUrl'].toString(); return Padding( padding: EdgeInsets.symmetric(vertical: 10), child: Dismissible( key: Key(documentSnapshot.id), direction: DismissDirection.endToStart, onDismissed: (direction) { // 删除商品逻辑 FirebaseFirestore.instance .collection('users') .doc(userid) .collection('cart') .doc(documentSnapshot.id) .delete(); }, background: Container( padding: EdgeInsets.symmetric(horizontal: 20), decoration: BoxDecoration( color: Color(0xFFFFE6E6), borderRadius: BorderRadius.circular(15), ), child: Row( children: [ Spacer(), Icon(Icons.delete), ], ), ), child: Container( padding: EdgeInsets.all( getProportionateScreenWidth(8)), decoration: BoxDecoration( color: Colors.white , borderRadius: BorderRadius.circular(15), ), child: Row( children: [ SizedBox( width: 88, child: AspectRatio( aspectRatio: 0.88, child: Container( padding: EdgeInsets.all( getProportionateScreenWidth(5)), decoration: BoxDecoration( color: Colors.black, borderRadius: BorderRadius.circular(10), image: DecorationImage(image: NetworkImage(imgurl), fit: BoxFit.cover) ), ), ), ), SizedBox(width: 20), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "${documentSnapshot['brand']} ${documentSnapshot['name']}", style: TextStyle( color: Colors.black, fontSize: 16), maxLines: 2, ), SizedBox(height: 10), Text.rich( TextSpan( // 这里显示当前商品的单价,不是累加值 text: "RM ${singleItemPrice.toStringAsFixed(2)}", style: TextStyle( fontWeight: FontWeight.w600, color: kPrimaryColor), ), ) ], ), Spacer(), Column( children: [ SizedBox(height: 30,), Text( "x 1", style: TextStyle( color: Colors.black, fontSize: 16), ), ], ), ], ), ), )); } ), ), // 底部总价展示区域 Container( padding: EdgeInsets.symmetric(vertical: 15), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("购物车总价", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), Text( "RM ${totalPrice.toStringAsFixed(2)}", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: kPrimaryColor), ) ], ), ) ], ), ); } return const Center( child: CircularProgressIndicator(), ); }); }
注意:
toStringAsFixed(2)是为了把价格格式化为2位小数,避免出现类似19.99999999的浮点精度显示问题。
内容的提问来源于stack exchange,提问作者Ahmad Silva
相关产品推荐
相关产品推荐

