Flutter中Future<QuerySnapshot>调用docs属性报错的解决咨询
解决Flutter Cloud Firestore异步查询错误
问题说明
开发一款展示用户附近ATM的Flutter应用时,编译代码遇到错误:
The getter 'docs' isn't defined for the type 'Future<QuerySnapshot<Map<String, dynamic>>>'.
错误出现在这一行:
List<DocumentSnapshot<Map<String, dynamic>>> documentList = querySnapshot().docs;
错误原因
querySnapshot()是异步函数,返回的是Future<QuerySnapshot>对象,而非直接的查询结果,不能直接访问.docs属性。- 在
build方法内直接执行异步数据获取和处理,违反Flutter生命周期规则,会引发重复执行、状态异常等问题。
解决方案
步骤1:重构异步逻辑
将数据查询和处理逻辑移到独立的异步方法中,或使用FutureBuilder管理异步状态,避免在build内直接处理异步操作。
步骤2:正确等待异步结果
使用await关键字等待异步查询完成,获取实际的QuerySnapshot实例后再访问.docs属性。
修正后的完整代码
import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'dart:math' show asin, cos, pi, pow, sin, sqrt; final _firestore = FirebaseFirestore.instance; class Principal extends StatefulWidget { const Principal({Key? key}) : super(key: key); @override State<Principal> createState() => _PrincipalState(); } class _PrincipalState extends State<Principal> { Position? position1; @override void initState() { super.initState(); getCurrentLocation(); } getCurrentLocation() async { Position position = await determinePosition(); setState(() { position1 = position; }); } Future<Position> determinePosition() async { LocationPermission permission; permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { return Future.error("Location permissions are denied"); } } return await Geolocator.getCurrentPosition(); } double _toRadians(double degree) { return degree * pi / 180; } double calculateDistance(double lat1, double lon1, double lat2, double lon2) { const double earthRadius = 6371; double dLat = _toRadians(lat2 - lat1); double dLon = _toRadians(lon2 - lon1); double a = pow(sin(dLat / 2), 2) + cos(_toRadians(lat1)) * cos(_toRadians(lat2)) * pow(sin(dLon / 2), 2); double c = 2 * asin(sqrt(a)); return earthRadius * c; } Future<List<ATM>> fetchNearbyATMs(GeoPoint userLocation, double radius) async { final querySnapshot = await _firestore .collection('atms') .where("isFunctioning", isEqualTo: true) .where('position.geopoint.latitude', isGreaterThan: userLocation.latitude - (radius / 111)) .where('position.geopoint.latitude', isLessThan: userLocation.latitude + (radius / 111)) .get(); List<ATM> atmList = []; for (DocumentSnapshot<Map<String, dynamic>> document in querySnapshot.docs) { GeoPoint atmLocation = document.get('position.geopoint'); double distance = calculateDistance( userLocation.latitude, userLocation.longitude, atmLocation.latitude, atmLocation.longitude); if (distance <= radius) { ATM atm = ATM( name: document.get('name'), isFunctioning: document.get('isFunctioning'), position: GeoPoint(atmLocation.latitude, atmLocation.longitude), distance: distance, ); atmList.add(atm); } } atmList.sort((a, b) => a.distance.compareTo(b.distance)); return atmList; } @override Widget build(BuildContext context) { if (position1 == null) { return Scaffold( appBar: AppBar( title: const Text('ATMs'), ), body: const Center( child: CircularProgressIndicator(color: Color(0xFFee7b64)), ), ); } else { GeoPoint userLocation = GeoPoint(position1!.latitude, position1!.longitude); double radius = 10; return Scaffold( appBar: AppBar( title: const Text('ATMs'), ), body: FutureBuilder<List<ATM>>( future: fetchNearbyATMs(userLocation, radius), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center( child: CircularProgressIndicator(color: Color(0xFFee7b64)), ); } else if (snapshot.hasError) { return Center(child: Text('加载失败: ${snapshot.error}')); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { return const Center(child: Text('附近没有可用的ATM')); } else { return ListView.builder( itemCount: snapshot.data!.length, itemBuilder: (BuildContext context, int index) { ATM atm = snapshot.data![index]; return ListTile( title: Text(atm.name), subtitle: Text('${atm.distance.toStringAsFixed(2)} km'), ); }, ); } }, ), ); } } } class ATM { String name; bool isFunctioning; GeoPoint position; double distance; ATM({required this.name, required this.isFunctioning, required this.position, required this.distance}); }
关键修改点
- 将
getCurrentLocation()移到initState中,只在组件初始化时执行一次。 - 新增
fetchNearbyATMs异步方法,专门处理Firestore查询和ATM数据处理。 - 使用
FutureBuilder管理异步查询的状态,自动处理加载、成功、错误、空数据等场景。 - 正确使用
await获取Firestore查询结果,再访问.docs属性。
内容的提问来源于stack exchange,提问作者Abdel
相关产品推荐
相关产品推荐

