You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 08:27:03