Flutter FutureBuilder报错:_JsonQuerySnapshot无data方法求解
错误分析与解决方案
核心报错原因
报错NoSuchMethodError: Class '_JsonQuerySnapshot' has no instance method 'data'的直接原因是:
- 在
getOutletById方法中,错误请求了店铺下的products集合(返回QuerySnapshot对象),却试图调用只有DocumentSnapshot才有的data()方法。QuerySnapshot用于表示集合查询结果,没有data()方法,因此触发错误。 - 逻辑偏差:该方法目的是获取店铺详情,但实际请求的是店铺下的商品集合,完全偏离需求。
分步解决方案
1. 修复getOutletById方法(获取店铺详情)
修改方法逻辑,直接请求店铺文档本身,而非商品集合:
Future<Map<String, dynamic>> getOutletById(String outId) async { final Map<String, dynamic> idOutlet = {}; _outletMerchant.clear(); try { // 直接获取店铺文档,而非products子集合 final outletDoc = await outletsCollectionRef.doc(outId).get(); if (!outletDoc.exists) { print("店铺文档不存在"); return idOutlet; } // DocumentSnapshot拥有data()方法,可获取店铺数据 final outletResponse = outletDoc.data()!; final outletMerchantData = await firestore .collection(allUserCollection) .doc(outletResponse[merchantId]) .get(); final merchantResponse = outletMerchantData.data(); print('outlet merchant data-->>>> ${merchantResponse}'); if (merchantResponse != null) { _outletMerchant.addAll(merchantResponse); } idOutlet.addAll(outletResponse); } catch (err) { print(err); } return idOutlet; }
2. 修复商品列表的StreamBuilder逻辑
原getProductsAndImages方法错误将QuerySnapshot当作单个文档处理,同时ListView.builder缺少itemCount会导致无限循环:
第一步:修改getProductsAndImages方法
改为接收单个商品文档,直接处理数据:
Widget getProductsAndImages(BuildContext context, QueryDocumentSnapshot productDoc) { final productData = productDoc.data() as Map<String, dynamic>; final outletId = ModalRoute.of(context)!.settings.arguments as String; // 处理图片列表,避免空值和索引越界 List<String> productImgs = List.from(productData['productImageURL'] ?? []); String? imageUrl = productImgs.length > 4 ? productImgs[4] : null; return ProductItem( productId: productDoc.id, // 商品ID为文档ID productName: productData['productName'] ?? '无名商品', productDescription: productData['prodDesc'] ?? '', productPrice: double.tryParse(productData['productPrice'].toString()) ?? 0.0, images: imageUrl, category: productData['category'] ?? '', count: double.tryParse(productData['countInStock'].toString()) ?? 0.0, outletId: outletId ); }
第二步:替换原ListView.builder为带StreamBuilder的列表
在FutureBuilder的Column中,将原ListView.builder替换为:
StreamBuilder<QuerySnapshot>( stream: productsRef.doc(outletId).collection('products').snapshots(), builder: (ctx, productSnapshot) { if (!productSnapshot.hasData) { return const Center(child: Text('加载商品中...')); } final products = productSnapshot.data!.docs; return ListView.builder( padding: EdgeInsets.zero, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: products.length, // 必须设置itemCount,避免无限循环 itemBuilder: (ctx, index) { return getProductsAndImages(context, products[index]); }, ); }, )
3. 完善FutureBuilder的空值处理
原代码中判断snapshot.data.length == 0不合理(返回的是Map,应使用isEmpty),同时添加空值默认值避免崩溃:
builder: (ctx, AsyncSnapshot<Map<String, dynamic>> snapshot) { if (snapshot.connectionState == ConnectionState.waiting || snapshot.data == null) { return const Center(child: Text("加载中...")); } final outletData = snapshot.data!; if (outletData.isEmpty) { return const Center(child: Text("未找到店铺信息")); } return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ ShopHeader( outletName: outletData['outletName'] ?? '无名店铺', outletImage: outletData['outletImageURL'] ?? '', location: outletData['location'] ?? '', description: outletData['description'] ?? '', phoneNum: outletData['phone'] ?? '', merchantId: outletData['merchantId'] ?? '', outletId: outletId, ), // 其余代码保持不变 ], ), ); },
内容的提问来源于stack exchange,提问作者HaKim
相关产品推荐
相关产品推荐

