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

Flutter结合Firebase Firestore实现附近营业餐厅查询问题求助

解决Flutter中Firestore查询附近营业餐厅并列表展示的问题

1. 调整Firestore数据结构

先把数据库中分开的latitude、longitude字段合并成GeoPoint类型的location字段,保留原有的Name、isFunctional字段,这样才能利用Firestore的地理查询能力。

2. 添加依赖

在pubspec.yaml中添加最新稳定版的依赖:

dependencies:
  flutter:
    sdk: flutter
  cloud_firestore: ^4.14.0
  geolocator: ^10.1.0

3. 实现位置查询逻辑

获取用户位置

封装获取用户位置的方法,处理权限和服务状态:

import 'package:geolocator/geolocator.dart';

Future<Position> getUserLocation() async {
  // 检查位置服务是否开启
  bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
  if (!serviceEnabled) {
    return Future.error('位置服务未开启');
  }

  // 检查并请求位置权限
  LocationPermission permission = await Geolocator.checkPermission();
  if (permission == LocationPermission.denied) {
    permission = await Geolocator.requestPermission();
    if (permission == LocationPermission.denied) {
      return Future.error('位置权限被拒绝');
    }
  }
  if (permission == LocationPermission.deniedForever) {
    return Future.error('位置权限永久拒绝,无法获取位置');
  }

  return await Geolocator.getCurrentPosition();
}

查询附近营业餐厅

通过矩形范围初步筛选,再计算精确距离并排序:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:math';

Future<List<Map<String, dynamic>>> getNearbyRestaurants(double radiusInKm) async {
  final userPosition = await getUserLocation();
  final userGeoPoint = GeoPoint(userPosition.latitude, userPosition.longitude);

  // 计算经纬度范围(1纬度≈111公里,经度距离随纬度变化)
  final latDelta = radiusInKm / 111;
  final lonDelta = radiusInKm / (111 * cos(userPosition.latitude * pi / 180));

  final southwest = GeoPoint(userPosition.latitude - latDelta, userPosition.longitude - lonDelta);
  final northeast = GeoPoint(userPosition.latitude + latDelta, userPosition.longitude + lonDelta);

  // Firestore查询:筛选营业中且位置在矩形范围内的餐厅
  final querySnapshot = await FirebaseFirestore.instance
      .collection('restaurants') // 替换成你的集合名称
      .where('isFunctional', isEqualTo: true)
      .where('location', isGreaterThanOrEqualTo: southwest)
      .where('location', isLessThanOrEqualTo: northeast)
      .get();

  // 筛选出半径内的餐厅并计算距离
  final nearbyRestaurants = <Map<String, dynamic>>[];
  for (var doc in querySnapshot.docs) {
    final restaurantLocation = doc['location'] as GeoPoint;
    final distance = Geolocator.distanceBetween(
      userPosition.latitude,
      userPosition.longitude,
      restaurantLocation.latitude,
      restaurantLocation.longitude,
    );

    if (distance <= radiusInKm * 1000) { // 转换为米对比
      nearbyRestaurants.add({
        'id': doc.id,
        'name': doc['Name'],
        'distance': distance / 1000, // 转换为公里
        ...doc.data(),
      });
    }
  }

  // 按距离从近到远排序
  nearbyRestaurants.sort((a, b) => a['distance'].compareTo(b['distance']));

  return nearbyRestaurants;
}

4. 列表展示

用FutureBuilder结合ListView展示查询结果:

import 'package:flutter/material.dart';

class NearbyRestaurantsScreen extends StatelessWidget {
  const NearbyRestaurantsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('附近营业餐厅')),
      body: FutureBuilder<List<Map<String, dynamic>>>(
        future: getNearbyRestaurants(5), // 查询5公里内的餐厅
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }

          if (snapshot.hasError) {
            return Center(child: Text('加载失败:${snapshot.error}'));
          }

          final restaurants = snapshot.data ?? [];
          if (restaurants.isEmpty) {
            return const Center(child: Text('附近没有营业的餐厅'));
          }

          return ListView.builder(
            itemCount: restaurants.length,
            itemBuilder: (context, index) {
              final restaurant = restaurants[index];
              return ListTile(
                title: Text(restaurant['Name']),
                subtitle: Text('距离:${restaurant['distance'].toStringAsFixed(2)}公里'),
                // 可添加更多展示字段,比如地址、评分等
              );
            },
          );
        },
      ),
    );
  }
}

关键注意事项

  • 创建Firestore索引:需要在Firestore控制台创建复合索引,集合选择你的餐厅集合,字段选isFunctional(升序)和location(升序),否则地理范围查询会报错。
  • 权限处理:必须确保用户授予位置权限,否则无法获取用户位置进行查询。
  • 圆形范围优化:Firestore不直接支持圆形地理查询,这里用矩形初步筛选后再计算精确距离,是性价比最高的方案。

内容的提问来源于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 23:02:43