Flutter从Firebase Cloud Firestore获取产品数据后网格视图重复显示首个产品
问题场景
- 已向Cloud Firestore的
kollamProducts集合上传2条产品详情,计划以网格瓦片形式做产品展示 - 已编写
getData方法从Firestore拉取数据,打印文档长度可正确得到数值2,对应实现代码:
void getData() async { QuerySnapshot snapshot = await FirebaseFirestore.instance.collection('kollamProducts').get(); snapshot.docs.forEach((document) { category = document['category']; id = document['id']; name = document['name']; shop = document['shop']; quantity = document['quantity']; images = List.from(document['images']); sizes = List.from(document['sizes']); }); if (snapshot.docs.length != 0) { setState(() { prodL = snapshot.docs.length; }); } }
现有展示组件实现
网格布局组件
组件通过GridView.builder实现网格渲染,在itemBuilder逻辑中调用SingleProduct组件渲染每个网格项,对应代码:
Widget build(BuildContext context) { return GridView.builder( physics: NeverScrollableScrollPhysics(), shrinkWrap: false, primary: false, itemCount: prodL, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, ), itemBuilder: (BuildContext context, int index) { return SingleProdc( product_name: name, product_picture: images[0], product_image1: images[1], product_image2: images[2], product_shop: shop, ); }, ); }
单个产品卡片组件
SingleProdc组件将产品信息整合在Card卡片中,适配网格瓦片的展示逻辑,对应代码:
class SingleProdc extends StatelessWidget { final product_name; final product_picture; final product_image1; final product_image2; final product_shop; SingleProdc( {this.product_name, this.product_picture, this.product_image1, this.product_image2, this.product_shop}); @override Widget build(BuildContext context) { return Card( child: Hero( tag: product_name, child: Material( borderRadius: BorderRadius.circular(50), child: InkWell( borderRadius: BorderRadius.circular(50), onTap: () => Navigator.of(context).push(MaterialPageRoute( builder: (context) => ProductDetails( product_detail_name: product_name, product_detail_picture: product_picture, product_detail_image1: product_image1, product_detail_image2: product_image2, product_detail_shop: product_shop, ))), child: GridTile( header: Container( height: 30, color: Colors.black38, child: Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.home, color: Colors.white, ), Text( product_shop, style: GoogleFonts.poppins(color: Colors.white), ), ], )), ), footer: Container( height: 50, color: Colors.black38, child: Column( children: [ Text( product_name, style: GoogleFonts.poppins( color: Colors.white, fontSize: product_name.toString().length > 9 ? 10 : 15, ), ), Text( "\u{20B9} 500", style: GoogleFonts.poppins( color: Colors.white, fontSize: 15), ), ], )), child: Image.network( product_picture, fit: BoxFit.cover, ), ), ), ), ), ); } }
异常现象
- 网格渲染环节没有正确展示2款不同产品,反而出现同一款产品被重复渲染的问题
- 日志已确认
snapshot.docs.length正确识别到2条产品数据,但所有网格项的内容完全一致
问题根因
- 数据存储逻辑错误:
getData方法中通过forEach遍历文档时,直接把每条文档的赋值给全局单变量(category、id、name、shop、images、sizes),每一次循环都会覆盖上一次循环的赋值,遍历结束后这些全局变量仅保留最后一次循环写入的内容,没有把多条产品数据单独留存。 - 组件渲染取数逻辑错误:
GridView的itemBuilder构建每个网格项时,没有根据传入的index参数获取对应下标的产品数据,反而直接引用上述全局变量,导致不管渲染第几个网格项,拿到的都是同一组被覆盖后的全局值,最终出现同一款产品重复渲染的效果。
修复方案
- 新增产品数据模型类,规范化存储单条产品的全量信息,同时在页面状态中定义产品列表变量,替代原来的全局单变量:
// 产品数据模型 class Product { final String category; final String id; final String name; final String shop; final int quantity; final List<String> images; final List<String> sizes; Product({ required this.category, required this.id, required this.name, required this.shop, required this.quantity, required this.images, required this.sizes, }); } // 页面中定义的状态变量 List<Product> productList = []; - 重写
getData方法的遍历逻辑,把每条文档转成Product实例存入产品列表,替换原有的全局单变量赋值逻辑:void getData() async { QuerySnapshot snapshot = await FirebaseFirestore.instance.collection('kollamProducts').get(); productList.clear(); snapshot.docs.forEach((document) { final docData = document.data() as Map<String, dynamic>; productList.add(Product( category: docData['category'], id: docData['id'], name: docData['name'], shop: docData['shop'], quantity: docData['quantity'], images: List.from(docData['images']), sizes: List.from(docData['sizes']), )); }); if (productList.isNotEmpty) { setState(() {}); } } - 调整GridView的渲染逻辑,将
itemCount替换为productList.length,同时在itemBuilder中根据index取对应下标的产品数据传给SingleProdc组件:itemCount: productList.length, itemBuilder: (BuildContext context, int index) { final currentProduct = productList[index]; return SingleProdc( product_name: currentProduct.name, product_picture: currentProduct.images[0], product_image1: currentProduct.images[1], product_image2: currentProduct.images[2], product_shop: currentProduct.shop, ); },
额外注意点
Hero组件要求tag全局唯一,原代码直接使用product_name作为tag,遇到同名产品时会触发报错,建议拼接产品唯一id生成tag,比如'product_${currentProduct.id}'- 取图片数组下标时增加边界判断,避免部分产品的
images数组长度不足3时抛出数组越界错误
内容的提问来源于stack exchange,提问作者AliMohammed Manzoor
相关产品推荐
相关产品推荐

