Flutter中的地理定位和地理编码

1,720 阅读7分钟

如今,发现用户的位置是移动应用程序中一个非常普遍和强大的用例。如果你曾经试图在Android中实现位置,你知道模板代码会变得多么复杂和混乱。

但Flutter不是这样的--它有很多惊人的包,为你抽象出模板代码,使实现地理位置成为一个梦想。另一个亮点是,你可以在Android和iOS上获得这些功能。

让我们快速浏览一下我们今天正在构建的收集位置数据的功能。

Flutter geolocation demo

本文将带领你了解两个最受欢迎的、易于使用的地理定位的Flutter包。

让我们从位置开始,这是一个Flutter最喜欢的包。这个包简单得不能再简单了。只需三个简单的步骤,您就可以获得当前用户的位置以及处理位置权限。

前提条件

在开始之前,让我们快速检查一下我们需要的东西。

  • Flutter SDK
  • 一个编辑器;您可以使用Visual Code或Android Studio
  • 至少对Flutter有初级的了解

基本上就是这些了!

使用 Flutter 定位包

设置

将依赖关系添加到您的pubspec.yaml 文件中。

dependencies:
    location: ^4.2.0

由于Android和iOS处理权限的方式不同,我们必须在每个平台上分别添加。

对于安卓系统

AndroidManifest.xml 中添加以下位置权限。

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

如果你想在后台也访问用户的位置,在后台访问位置之前使用enableBackgroundMode({bool enable}) API,并在清单文件中也添加后台权限。

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>

对于iOS

Info.plist 中添加以下位置权限。

<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs to access your location</string>

NSLocationWhenInUseUsageDescription 是您唯一需要的权限。这也允许你访问后台位置,唯一的注意事项是,当应用程序在后台访问位置时,状态栏中会显示一个蓝色徽章。与安卓不同的是,我们在安卓系统中为在后台访问用户的位置添加了单独的权限。

位置权限

在请求用户位置之前,我们需要检查位置服务状态和权限状态,使用这几行代码就可以轻松完成。

Location location = new Location();

bool _serviceEnabled;
PermissionStatus _permissionGranted;

_serviceEnabled = await location.serviceEnabled();
if (!_serviceEnabled) {
 _serviceEnabled = await location.requestService();
 if (!_serviceEnabled) {
   return null;
 }
}

_permissionGranted = await location.hasPermission();
if (_permissionGranted == PermissionStatus.denied) {
 _permissionGranted = await location.requestPermission();
 if (_permissionGranted != PermissionStatus.granted) {
   return null;
 }
}

首先,我们创建一个由location 包提供的Location() 对象,该对象又为我们提供了两个有用的方法。serviceEnabled() 检查设备位置是否启用,或者用户是否手动禁用了它。

对于后者,我们显示一个本地提示,允许用户通过调用requestService() ,快速启用位置,然后我们再检查一次,如果他们随后从提示中启用了它。

一旦我们确定位置服务被启用,下一步就是通过调用hasPermission() that,检查我们的应用程序是否有必要的权限来使用它,该命令返回PermissionStatus

PermissionStatus 是一个枚举,可以有这三个值中的一个。

  • PermissionStatus.granted :位置服务的权限已被授予
  • PermissionStatus.denied :位置服务的权限已被拒绝
  • PermissionStatus.deniedForever :用户已经永远拒绝了位置服务的权限。这只适用于iOS。在这种情况下,requestPermission() 上不会显示任何对话框。

如果状态是denied, ,我们可以通过调用requestPermission() ,显示系统提示,要求获得位置权限。对于状态为granted ,我们可以立即访问位置,所以我们返回一个null

使用 location.enableBackgroundMode(enable: **true**)如果你想在后台访问用户的位置,也可以使用。

获取当前位置

如果定位服务是可用的,并且用户已经授予了定位权限,那么我们只需要两行代码就可以获得用户的位置--不,我不是在开玩笑。

LocationData _locationData;
_locationData = await location.getLocation();

LocationData 类提供了以下的位置信息。

class LocationData {
  final double latitude; // Latitude, in degrees
  final double longitude; // Longitude, in degrees
  final double accuracy; // Estimated horizontal accuracy of this location, radial, in meters
  final double altitude; // In meters above the WGS 84 reference ellipsoid
  final double speed; // In meters/second
  final double speedAccuracy; // In meters/second, always 0 on iOS
  final double heading; // Heading is the horizontal direction of travel of this device, in degrees
  final double time; // timestamp of the LocationData
  final bool isMock; // Is the location currently mocked
}

你也可以通过添加onLocationChanged 监听器来获得连续的回调,以便在用户位置变化时监听位置更新,这对于出租车应用、司机/骑手应用等来说是一个非常好的用例。

location.onLocationChanged.listen((LocationData currentLocation) {
  // current user location
});

注意,一旦你想停止监听更新,别忘了取消流订阅。

好了!现在我们有了当前的经度和纬度。现在我们有了用户位置的当前纬度和经度值。

让我们利用这些纬度和经度值来获取用户的完整地址或反向地理编码

为此,我们将使用另一个神奇的Flutter包:geocode

使用Flutter geocode包

设置

将该依赖关系添加到您的pubspec.yaml 文件中。

dependencies:
    geocode: 1.0.1

获取地址

获取地址再简单不过了。只要调用reverseGeocoding(latitude: lat, longitude: lang) 。这就是了!一个完整的带有空值检查的函数看起来像这样。

Future<String> _getAddress(double? lat, double? lang) async {
 if (lat == null || lang == null) return "";
 GeoCode geoCode = GeoCode();
 Address address =
     await geoCode.reverseGeocoding(latitude: lat, longitude: lang);
 return "${address.streetAddress}, ${address.city}, ${address.countryName}, ${address.postal}";
}

这不是很简单吗?

完整的代码看起来像这样。

class GetUserLocation extends StatefulWidget {
 GetUserLocation({Key? key, required this.title}) : super(key: key);
 final String title;

 @override
 _GetUserLocationState createState() => _GetUserLocationState();
}

class _GetUserLocationState extends State<GetUserLocation> {
 LocationData? currentLocation;
 String address = "";

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(),
     body: Center(
       child: Padding(
         padding: EdgeInsets.all(16.0),
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: <Widget>[
             if (currentLocation != null)
               Text(
                   "Location: ${currentLocation?.latitude}, ${currentLocation?.longitude}"),
             if (currentLocation != null) Text("Address: $address"),
             MaterialButton(
               onPressed: () {
                 _getLocation().then((value) {
                   LocationData? location = value;
                   _getAddress(location?.latitude, location?.longitude)
                       .then((value) {
                     setState(() {
                       currentLocation = location;
                       address = value;
                     });
                   });
                 });
               },
               color: Colors.purple,
               child: Text(
                 "Get Location",
                 style: TextStyle(color: Colors.white),
               ),
             ),
           ],
         ),
       ),
     ),
   );
 }

 Future<LocationData?> _getLocation() async {
   Location location = new Location();
   LocationData _locationData;

   bool _serviceEnabled;
   PermissionStatus _permissionGranted;

   _serviceEnabled = await location.serviceEnabled();
   if (!_serviceEnabled) {
     _serviceEnabled = await location.requestService();
     if (!_serviceEnabled) {
       return null;
     }
   }

   _permissionGranted = await location.hasPermission();
   if (_permissionGranted == PermissionStatus.denied) {
     _permissionGranted = await location.requestPermission();
     if (_permissionGranted != PermissionStatus.granted) {
       return null;
     }
   }


   _locationData = await location.getLocation();

   return _locationData;
 }

 Future<String> _getAddress(double? lat, double? lang) async {
   if (lat == null || lang == null) return "";
   GeoCode geoCode = GeoCode();
   Address address =
       await geoCode.reverseGeocoding(latitude: lat, longitude: lang);
   return "${address.streetAddress}, ${address.city}, ${address.countryName}, ${address.postal}";
 }
}

常见的陷阱

尽管这些包使我们的生活变得更容易,我们不必处理在Android和iOS中原生访问位置的复杂过程,但有一些问题你可能会面临。让我们来看看这些问题以及可以帮助你补救这些问题的步骤。

  • 应用程序泄漏内存。如果你正在持续收听位置更新,请确保取消流媒体订阅,一旦你想停止收听更新,就必须取消。
  • 用户必须接受始终允许使用后台位置的位置权限。安卓11系统中的 "始终允许 "选项并没有出现在位置权限对话框提示中。用户必须从应用程序设置中手动启用它。
  • 用户可能已经在iOS上永远拒绝了定位,所以requestPermisssions() ,不会显示询问定位权限的本地提示。请确保处理这种边缘情况
  • 用户可能随时从应用设置中撤销位置权限,所以在访问位置数据之前,确保在应用恢复时检查它们。

结论

由于Flutter简化了对位置的访问,作为开发者的我们可能很想立即将其添加到我们的应用程序中。但与此同时,我们需要确保我们的应用程序确实符合请求用户位置的用例,并利用它为用户增加一些价值,而不仅仅是将位置数据发送到服务器上。

在即将到来的Android和iOS的操作系统版本中,随着安全和隐私的增加,访问位置数据而不为用户提供价值可能会使你的应用程序被商店拒绝。有很多好的用例可以使用用户的位置,例如,基于用户位置的个性化主屏幕,用于显示按用户当前位置的远近排序的食品/外卖应用。接机/送餐应用是最常见的用例。

你也可以在你真正想要使用的特定屏幕上询问用户的位置,而不是在主屏幕上立即询问。这对用户来说更清楚,他们也就更不可能拒绝位置权限。

谢谢你的坚持,祝你编码愉快,伙计!你可以在GitHub上访问文章中使用的样本应用程序。

The postGeolocation and geocoding in Flutterappeared first onLogRocket Blog.