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

结合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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 15:55:07