结合State Provider使用Geolocator.distanceBetween及定位丢失问题
问题:沙龙目录APP定位数据丢失问题
原始问题
正在开发一款沙龙目录APP,会获取用户位置并结合Firebase数据库中的沙龙位置,计算用户到沙龙的距离。用户注册时请求位置权限,定位信息存储在UserDetailsModel中,通过Provider管理全APP状态。
当前问题:注册页面能成功获取并打印用户经纬度,但首页的PopularSalonswidget无法获取这些值,沙龙的经纬度则能正常获取。
更新说明
新用户注册流程中计算代码可正常获取userLat和userLng;但重启APP或跳转页面返回后,用户定位数据丢失,组件消失,推测与PopularSalonswidget中的这段代码有关:
Widget build(BuildContext context) { UserDetailsModel userDetails = Provider.of<UserDetailsProvider>(context).userDetails; if (salons.isEmpty) { return const Center( child: CupertinoActivityIndicator(), );
相关代码
注册页面代码
class SignUpScreen extends StatefulWidget { @override _SignUpScreenState createState() => _SignUpScreenState(); } class _SignUpScreenState extends State<SignUpScreen> with SingleTickerProviderStateMixin { @override void initState() { super.initState(); _determinePosition(); } Position? position; String userLocation = ''; late final Salon? salon; Future<void> _determinePosition() async { try { bool serviceEnabled; LocationPermission permission; serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { throw Exception('Location services are disabled.'); } permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { throw Exception('Location permissions are denied'); } } if (permission == LocationPermission.deniedForever) { throw Exception( 'Location permissions are permanently denied, we cannot request permissions.'); } Position newPosition = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high, ); setState(() { position = newPosition; }); _updateUserDetails(position); } catch (error) { print('Error determining position: $error'); } } Future<void> _updateUserDetails(Position? position) async { if (position != null) { List<Placemark> placemark = await placemarkFromCoordinates( position.latitude, position.longitude, ); Placemark place = placemark[0]; cityController.text = '${place.street}, ${place.locality}'; double userLat = position.latitude; double userLng = position.longitude; // Update user details using Provider Provider.of<UserDetailsProvider>(context, listen: false) .updateUserCity('${place.street}, ${place.locality}'); Provider.of<UserDetailsProvider>(context, listen: false) .updateUserLat(userLat); Provider.of<UserDetailsProvider>(context, listen: false) .updateUserLng(userLng); print(userLng); //works as expected print(userLat); //works as expected } }
PopularSalonswidget代码
class PopularSalonswidget extends StatefulWidget { final Salon? salon; final Function(int) onNoOfReviewUpdated; const PopularSalonswidget({ Key? key, this.salon, required this.onNoOfReviewUpdated, }) : super(key: key); @override State<PopularSalonswidget> createState() => _PopularSalonswidgetState(); } class _PopularSalonswidgetState extends State<PopularSalonswidget> { final FirebaseFirestore db = FirebaseFirestore.instance; List<Salon> salons = []; late UserDetailsModel userDetails; Map<String, double> salonDistances = {}; @override void initState() { super.initState(); userDetails = Provider.of<UserDetailsProvider>(context, listen: false).userDetails; if (userDetails.userLat != null && userDetails.userLng != null) { _fetchSalons(); } } void _fetchSalons() { db.collection('salons').get().then((snapshot) { setState(() { salons = snapshot.docs.map((doc) => Salon.fromJson(doc.data())).toList(); for (var salon in salons) { final calculatedDistance = calculateDistance( userDetails.userLat!, userDetails.userLng!, salon.latitude, salon.longitude, ); print(userDetails.userLat); //getting 0.0 print(userDetails.userLng); //getting 0.0 print(salon.latitude); //works as expected salonDistances[salon.id] = calculatedDistance; print(calculatedDistance); } }); }); } @override void didChangeDependencies() { super.didChangeDependencies(); final newDetails = Provider.of<UserDetailsProvider>(context, listen: false).userDetails; if (newDetails != null && newDetails != userDetails) { userDetails = newDetails; if (userDetails.userLat != null && userDetails.userLng != null) { _fetchSalons(); } } } Stream<int> _getReviewCountStream(String salonId) { return db .collection('salons') .doc(salonId) .collection('reviews') .snapshots() .map((snapshot) => snapshot.docs.length); } //this is my calculator method double calculateDistance( double userLat, double userLng, double salonLat, double salonLng) { return Geolocator.distanceBetween(userLat, userLng, salonLat, salonLng); }
解决方案
1. 持久化用户定位数据
Provider数据仅存于内存,重启APP会丢失,需用SharedPreferences持久化存储:
在注册页面的_updateUserDetails方法中,更新Provider的同时保存到本地:
// 添加SharedPreferences保存逻辑 final prefs = await SharedPreferences.getInstance(); await prefs.setDouble('userLat', userLat); await prefs.setDouble('userLng', userLng); await prefs.setString('userCity', '${place.street}, ${place.locality}');
在UserDetailsProvider初始化时读取本地数据恢复状态:
class UserDetailsProvider extends ChangeNotifier { UserDetailsModel userDetails = UserDetailsModel(); UserDetailsProvider() { _loadSavedUserDetails(); } Future<void> _loadSavedUserDetails() async { final prefs = await SharedPreferences.getInstance(); final userLat = prefs.getDouble('userLat'); final userLng = prefs.getDouble('userLng'); final userCity = prefs.getString('userCity'); if (userLat != null && userLng != null) { userDetails.userLat = userLat; userDetails.userLng = userLng; userDetails.userCity = userCity; notifyListeners(); } } // 保留原有update方法 void updateUserLat(double lat) { userDetails.userLat = lat; notifyListeners(); } void updateUserLng(double lng) { userDetails.userLng = lng; notifyListeners(); } void updateUserCity(String city) { userDetails.userCity = city; notifyListeners(); } }
2. 修复PopularSalonswidget的状态监听问题
方法一:用Consumer组件响应状态变化
重构build方法,通过Consumer自动监听Provider状态更新:
@override Widget build(BuildContext context) { return Consumer<UserDetailsProvider>( builder: (context, provider, child) { final userDetails = provider.userDetails; // 用户定位未就绪时显示加载 if (userDetails.userLat == null || userDetails.userLng == null) { return const Center(child: CupertinoActivityIndicator()); } // 沙龙数据未加载时触发加载 if (salons.isEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) _fetchSalonsWithUserDetails(userDetails); }); return const Center(child: CupertinoActivityIndicator()); } // 渲染沙龙列表(此处替换为你的实际列表代码) return ListView.builder( itemCount: salons.length, itemBuilder: (context, index) { final salon = salons[index]; final distance = salonDistances[salon.id] ?? 0.0; return ListTile( title: Text(salon.name), subtitle: Text('距离:${distance.toStringAsFixed(2)}米'), ); }, ); }, ); } // 修改_fetchSalons方法,接收最新的userDetails void _fetchSalonsWithUserDetails(UserDetailsModel userDetails) { db.collection('salons').get().then((snapshot) { if (!mounted) return; setState(() { salons = snapshot.docs.map((doc) => Salon.fromJson(doc.data())).toList(); salonDistances.clear(); for (var salon in salons) { final calculatedDistance = calculateDistance( userDetails.userLat!, userDetails.userLng!, salon.latitude, salon.longitude, ); salonDistances[salon.id] = calculatedDistance; } }); }); }
方法二:修复didChangeDependencies逻辑
将监听改为listen: true,确保Provider状态变化时触发更新:
@override void didChangeDependencies() { super.didChangeDependencies(); final newDetails = Provider.of<UserDetailsProvider>(context).userDetails; // 仅当经纬度有效且发生变化时加载沙龙 if (newDetails.userLat != null && newDetails.userLng != null && (newDetails.userLat != userDetails.userLat || newDetails.userLng != userDetails.userLng)) { userDetails = newDetails; _fetchSalons(); } }
3. APP启动时检查定位数据
在APP入口(如首页initState)检查本地是否有保存的定位数据,无数据则重新请求:
// 在首页State类中添加 @override void initState() { super.initState(); _checkAndRestoreLocation(); } Future<void> _checkAndRestoreLocation() async { final prefs = await SharedPreferences.getInstance(); final userLat = prefs.getDouble('userLat'); final userLng = prefs.getDouble('userLng'); if (userLat == null || userLng == null) { // 复用注册页面的_determinePosition方法获取定位 await _determinePosition(); } } // 复制注册页面的_determinePosition和_updateUserDetails方法到当前类
内容的提问来源于stack exchange,提问作者SFZ
相关产品推荐
相关产品推荐

