Flutter地理围栏插件geofencing_api的使用

发布于 1周前 作者 bupafengyu 来自 Flutter

Flutter地理围栏插件geofencing_api的使用

插件概述

geofencing_api插件用于实现圆形和多边形地理围栏服务。此插件不依赖平台原生的地理围栏API,因此不能保证电池效率。然而,它可以提供更准确和实时的地理围栏服务,只要应用程序处于活动状态。

注意:该插件不使用平台提供的地理围栏API实现,因此无法保证电池效率。相反,当应用程序处于活动状态时,它可以通过导航位置来提供更准确和实时的地理围栏服务。

主要特性

  • 可创建圆形类型的地理围栏。
  • 可创建多边形类型的地理围栏。
  • 可实时监听地理围栏状态变化。
  • 可实时监听位置变化。
  • 可请求或检查位置权限。

入门指南

添加依赖

pubspec.yaml文件中添加geofencing_api作为依赖项:

dependencies:
  geofencing_api: ^2.0.0

平台特定配置

Android

打开AndroidManifest.xml文件,并在<manifest><application>标签之间声明权限:

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

如果需要在后台运行地理围栏服务,还需声明以下权限:

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

iOS

打开ios/Runner/Info.plist文件,并在<dict>标签内声明描述:

<key>NSLocationWhenInUseUsageDescription</key>
<string>Used to collect location data.</string>

若需在后台收集位置数据,则还需添加以下描述:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Used to collect location data in the background.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Used to collect location data in thebackground.</string>
<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
    <string>location</string>
</array>

使用方法

请求位置权限

开始地理围栏服务前,必须确保已获得适当的位置权限(alwayswhileInUse)。以下是示例代码:

Future<bool> requestLocationPermission({bool background = false}) async {
  if (!await Geofencing.instance.isLocationServicesEnabled) {
    return false;
  }

  LocationPermission permission = await Geofencing.instance.getLocationPermission();
  if (permission == LocationPermission.denied) {
    permission = await Geofencing.instance.requestLocationPermission();
  }

  if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
    return false;
  }

  if (kIsWeb || kIsWasm) {
    return true;
  }

  if (Platform.isAndroid && background && permission == LocationPermission.whileInUse) {
    permission = await Geofencing.instance.requestLocationPermission();

    if (permission != LocationPermission.always) {
      return false;
    }
  }

  return true;
}

设置地理围栏服务

设置地理围栏服务时可以配置以下参数:

  • interval: 更新地理围栏状态的时间间隔,默认为5000毫秒。
  • accuracy: 地理围栏服务的精度,默认为100米。
  • statusChangeDelay: 状态变化延迟时间,默认为10000毫秒。
  • allowsMockLocation: 是否允许模拟位置,默认为false
  • printsDebugLog: 是否打印调试日志,默认为true
void setupGeofencing() {
  Geofencing.instance.setup(
    interval: 5000,
    accuracy: 100,
    statusChangeDelay: 10000,
    allowsMockLocation: false,
    printsDebugLog: true,
  );
}

创建地理围栏区域

创建地理围栏区域时可以使用.circular.polygon构造函数:

final Set<GeofenceRegion> _regions = {
  GeofenceRegion.circular(
    id: 'circular_region',
    data: {
      'name': 'National Museum of Korea',
    },
    center: const LatLng(37.523085, 126.979619),
    radius: 250,
    loiteringDelay: 60 * 1000,
  ),
  GeofenceRegion.polygon(
    id: 'polygon_region',
    data: {
      'name': 'Gyeongbokgung Palace',
    },
    polygon: [
      const LatLng(37.583696, 126.973739),
      const LatLng(37.583441, 126.979361),
      const LatLng(37.582506, 126.980198),
      const LatLng(37.579054, 126.979490),
      const LatLng(37.576112, 126.979061),
      const LatLng(37.576503, 126.974126),
      const LatLng(37.580959, 126.973568),
    ],
    loiteringDelay: 60 * 1000,
  ),
};

启动地理围栏服务

启动地理围栏服务时可以添加地理围栏区域并设置监听器:

void startGeofencing() async {
  Geofencing.instance.addGeofenceStatusChangedListener(_onGeofenceStatusChanged);
  Geofencing.instance.addGeofenceErrorCallbackListener(_onGeofenceError);

  await Geofencing.instance.start(regions: _regions);
}

Future<void> _onGeofenceStatusChanged(
  GeofenceRegion geofenceRegion,
  GeofenceStatus geofenceStatus,
  Location location,
) async {
  final String regionId = geofenceRegion.id;
  final String statusName = geofenceStatus.name;
  print('region(id: $regionId) $statusName');
}

void _onGeofenceError(Object error, StackTrace stackTrace) {
  print('error: $error\n$stackTrace');
}

增删地理围栏区域

即使服务已启动,也可以动态增删地理围栏区域:

void addRegions() {
  Geofencing.instance.addRegion(GeofenceRegion);
  Geofencing.instance.addRegions(Set<GeofenceRegion>);
}

void removeRegions() {
  Geofencing.instance.removeRegion(GeofenceRegion);
  Geofencing.instance.removeRegions(Set<GeofenceRegion>);
  Geofencing.instance.removeRegionById(String);
  Geofencing.instance.clearAllRegions();
}

暂停与恢复服务

暂停或恢复地理围栏服务:

void pauseGeofencing() {
  Geofencing.instance.pause();
}

void resumeGeofencing() {
  Geofencing.instance.resume();
}

停止服务

停止地理围栏服务时可以选择是否保留已添加的区域:

void stopGeofencing() async {
  Geofencing.instance.removeGeofenceStatusChangedListener(_onGeofenceStatusChanged);
  Geofencing.instance.removeGeofenceErrorCallbackListener(_onGeofenceError);

  await Geofencing.instance.stop(keepsRegions: true);
}

示例Demo

下面是一个完整的示例Demo,展示了如何结合Google Maps使用geofencing_api插件。

import 'dart:developer' as dev;
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:geofencing_api/geofencing_api.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart' as map;

const Color _kEnterColor = Color(0xFF4CAF50);
const Color _kExitColor = Color(0xFFF44336);
const Color _kDwellColor = Color(0xFF9C27B0);

final Set<GeofenceRegion> _regions = {
  GeofenceRegion.circular(
    id: 'region_1',
    data: {
      'name': 'National Museum of Korea',
    },
    center: const LatLng(37.523085, 126.979619),
    radius: 250,
    loiteringDelay: 60 * 1000,
  ),
  // 更多区域...
};

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);

  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: DemoPage(),
    ),
  );
}

class DemoPage extends StatefulWidget {
  const DemoPage({super.key});

  @override
  State<StatefulWidget> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
  Future<bool> _requestLocationPermission({bool background = false}) async {
    if (!await Geofencing.instance.isLocationServicesEnabled) {
      return false;
    }

    LocationPermission permission = await Geofencing.instance.getLocationPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geofencing.instance.requestLocationPermission();
    }

    if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
      return false;
    }

    if (kIsWeb || kIsWasm) {
      return true;
    }

    if (Platform.isAndroid && background && permission == LocationPermission.whileInUse) {
      permission = await Geofencing.instance.requestLocationPermission();

      if (permission != LocationPermission.always) {
        return false;
      }
    }

    return true;
  }

  void _setupGeofencing() {
    try {
      Geofencing.instance.setup(
        interval: 5 * 1000,
        accuracy: 100,
        statusChangeDelay: 10 * 1000,
        allowsMockLocation: true,
        printsDebugLog: true,
      );
    } catch (e, s) {
      _onError(e, s);
    }
  }

  void _startGeofencing() async {
    try {
      Geofencing.instance.addGeofenceStatusChangedListener(_onGeofenceStatusChanged);
      Geofencing.instance.addGeofenceErrorCallbackListener(_onError);

      await Geofencing.instance.start(regions: _regions);

      _refreshPage();
    } catch (e, s) {
      _onError(e, s);
    }
  }

  void _stopGeofencing() async {
    try {
      Geofencing.instance.removeGeofenceStatusChangedListener(_onGeofenceStatusChanged);
      Geofencing.instance.removeGeofenceErrorCallbackListener(_onError);

      await Geofencing.instance.stop();

      _refreshPage();
    } catch (e, s) {
      _onError(e, s);
    }
  }

  void _refreshPage() {
    if (mounted) {
      setState(() {});
    }
  }

  Future<void> _onGeofenceStatusChanged(
    GeofenceRegion geofenceRegion,
    GeofenceStatus geofenceStatus,
    Location location,
  ) async {
    final String regionId = geofenceRegion.id;
    final String statusName = geofenceStatus.name;
    dev.log('region(id: $regionId) $statusName');

    _refreshPage();
  }

  void _onError(Object error, StackTrace stackTrace) {
    dev.log('error: $error\n$stackTrace');
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      await _requestLocationPermission();
      _setupGeofencing();
      _startGeofencing();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GoogleMapView(
        regions: Geofencing.instance.regions,
      ),
    );
  }

  @override
  void dispose() {
    _stopGeofencing();
    super.dispose();
  }
}

class GoogleMapView extends StatefulWidget {
  const GoogleMapView({
    super.key,
    required this.regions,
  });

  final Set<GeofenceRegion> regions;

  @override
  State<StatefulWidget> createState() => _GoogleMapViewState();
}

class _GoogleMapViewState extends State<GoogleMapView> {
  final Set<map.Circle> _circles = {};
  final Set<map.Polygon> _polygons = {};

  Color _getFillColorByStatus(GeofenceStatus status) {
    switch (status) {
      case GeofenceStatus.enter:
        return _kEnterColor.withOpacity(0.5);
      case GeofenceStatus.exit:
        return _kExitColor.withOpacity(0.5);
      case GeofenceStatus.dwell:
        return _kDwellColor.withOpacity(0.5);
    }
  }

  void _updateMapsObject() {
    _circles.clear();
    _polygons.clear();

    for (final GeofenceRegion region in widget.regions) {
      if (region is GeofenceCircularRegion) {
        _circles.add(map.Circle(
          circleId: map.CircleId(region.id),
          center: map.LatLng(region.center.latitude, region.center.longitude),
          radius: region.radius,
          strokeWidth: 2,
          strokeColor: Colors.black,
          fillColor: _getFillColorByStatus(region.status),
        ));
        continue;
      }

      if (region is GeofencePolygonRegion) {
        _polygons.add(map.Polygon(
          polygonId: map.PolygonId(region.id),
          points: region.polygon.map((e) => map.LatLng(e.latitude, e.longitude)).toList(),
          strokeWidth: 2,
          strokeColor: Colors.black,
          fillColor: _getFillColorByStatus(region.status),
        ));
        continue;
      }
    }
  }

  @override
  void initState() {
    super.initState();
    _updateMapsObject();
  }

  @override
  void didUpdateWidget(covariant GoogleMapView oldWidget) {
    _updateMapsObject();
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    return map.GoogleMap(
      initialCameraPosition: const map.CameraPosition(
        target: map.LatLng(37.5479, 126.9904),
        zoom: 12.5,
      ),
      circles: _circles,
      polygons: _polygons,
      myLocationEnabled: true,
      myLocationButtonEnabled: true,
    );
  }
}

以上就是关于geofencing_api插件的详细介绍及示例代码,希望对您有所帮助!如果您有任何问题或发现任何bug,请通过GitHub Issues反馈给我们。


更多关于Flutter地理围栏插件geofencing_api的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter地理围栏插件geofencing_api的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用geofencing_api插件来实现地理围栏功能的代码示例。这包括基本的设置、添加地理围栏以及处理进入和离开围栏区域的事件。

1. 添加依赖

首先,在你的pubspec.yaml文件中添加geofencing_api依赖:

dependencies:
  flutter:
    sdk: flutter
  geofencing_api: ^x.y.z  # 请替换为最新版本号

然后运行flutter pub get来安装依赖。

2. 配置Android权限

android/app/src/main/AndroidManifest.xml中添加必要的权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.geofencing">

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

    <application
        ...>
        <!-- 其他配置 -->
    </application>

</manifest>

3. 初始化插件并添加地理围栏

在你的Dart代码中,你可以按照以下步骤来初始化插件并添加地理围栏:

import 'package:flutter/material.dart';
import 'package:geofencing_api/geofencing_api.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late GeofencingClient _geofencingClient;

  @override
  void initState() {
    super.initState();
    _geofencingClient = GeofencingClient();
    _initializeGeofencing();
  }

  Future<void> _initializeGeofencing() async {
    // 请求权限(这部分通常在实际应用中会更复杂,包括处理权限请求的结果)
    bool hasPermissions = await _requestPermissions();

    if (hasPermissions) {
      // 添加地理围栏
      await _addGeofence();

      // 注册围栏事件监听器
      _geofencingClient.addGeofenceEventListener(_handleGeofenceEvent);
    }
  }

  Future<bool> _requestPermissions() async {
    // 这里应该使用更复杂的权限请求逻辑,比如使用permission_handler插件
    // 这里简单返回true以继续演示
    return true;
  }

  Future<void> _addGeofence() async {
    final Geofence geofence = Geofence(
      requestId: "my_geofence",
      circularRegion: CircularRegion(
        center: LatLng(37.7853889, -122.4056973),
        radius: 100.0,  // 半径,单位为米
      ),
      geofenceTransition: GeofenceTransition.ENTER | GeofenceTransition.EXIT,
      dwellingTime: Duration(seconds: 30),  // 停留时间
      notificationResponsiveness: Duration(seconds: 0),  // 通知响应性
    );

    try {
      await _geofencingClient.addGeofence(geofence);
      print("Geofence added successfully");
    } catch (e) {
      print("Failed to add geofence: $e");
    }
  }

  void _handleGeofenceEvent(GeofenceEvent event) {
    if (event.geofenceTransition == GeofenceTransition.ENTER) {
      print("Entered geofence: ${event.requestId}");
    } else if (event.geofenceTransition == GeofenceTransition.EXIT) {
      print("Exited geofence: ${event.requestId}");
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Geofencing Demo'),
        ),
        body: Center(
          child: Text('Check the console for geofence events.'),
        ),
      ),
    );
  }
}

4. 处理权限请求

在实际应用中,你需要更细致地处理权限请求,比如使用permission_handler插件来请求位置权限。这里为了简化,我们直接返回了true

5. 运行应用

确保你的设备或模拟器支持地理围栏功能(通常需要Android模拟器配置Google Play服务),然后运行你的Flutter应用。

这个示例展示了如何使用geofencing_api插件来添加地理围栏并处理进入和离开围栏区域的事件。根据你的具体需求,你可能需要调整代码中的参数和逻辑。

回到顶部