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
相关产品推荐
相关产品推荐

