Flutter地理围栏与前台服务插件geofence_foreground_service的使用

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

Flutter地理围栏与前台服务插件geofence_foreground_service的使用

geofence_foreground_service

Flutter Version License Platform Platform

geofence_foreground_service 是一个Flutter插件,它使您能够轻松地在Flutter应用中处理地理围栏事件。通过利用原生操作系统的API,在Android上创建一个前台服务以实现电池高效性(使用GeofenceWorkManagerAPI),在iOS上则使用CLLocationManager

特性

  • 支持在前台和后台进行地理围栏 💪
  • 地理围栏圆形区域 🗺️
  • 地理围栏多边形 🤯 可以使用坐标列表添加地理围栏,系统将计算它们的中心并注册,完整多边形支持正在进行中 🚧
  • 通知定制化 🔔 ⚠️Android⚠️ 显示通知是运行前台服务时必须的,您可以自定义显示的内容(标题、内容或图标),默认情况下,插件会显示您的应用图标。
  • 通知响应性 ⏱️ ⚠️Android⚠️ 您可以设置Android通知的响应性,详情请参考官方文档

设置

🔧 Android 设置

  1. 启用MultiDex,具体方法可以查看这里
  2. AndroidManifest.xml文件的应用标签内添加服务
<service 
    android:name="com.f2fk.geofence_foreground_service.GeofenceForegroundService"
    android:foregroundServiceType="location">
</service>
  1. 添加权限
<!--required-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />

<!--至少需要以下权限之一-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  1. 确保app/build.gradle文件中的minSdkVersion为29+

🔧 iOS 设置

  1. 导航到Podfile并确保iOS版本为12+

    platform :ios, '12.0'
    
  2. 确保向Info.plist添加以下权限

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app need your location to provide best feature based on location</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app need your location to provide best feature based on location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app need your location to provide best feature based on location</string>
  1. 打开XCode,启用Location updatesBackground fetch功能

示例代码

下面是一个完整的示例代码,演示了如何使用geofence_foreground_service插件:

import 'dart:async';
import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:geofence_foreground_service/constants/geofence_event_type.dart';
import 'package:geofence_foreground_service/exports.dart';
import 'package:geofence_foreground_service/geofence_foreground_service.dart';
import 'package:geofence_foreground_service/models/notification_icon_data.dart';
import 'package:geofence_foreground_service/models/zone.dart';
import 'package:permission_handler/permission_handler.dart';

@pragma('vm:entry-point')
void callbackDispatcher() async {
  GeofenceForegroundService().handleTrigger(
    backgroundTriggerHandler: (zoneID, triggerType) {
      log(zoneID, name: 'zoneID');

      if (triggerType == GeofenceEventType.enter) {
        log('enter', name: 'triggerType');
      } else if (triggerType == GeofenceEventType.exit) {
        log('exit', name: 'triggerType');
      } else if (triggerType == GeofenceEventType.dwell) {
        log('dwell', name: 'triggerType');
      } else {
        log('unknown', name: 'triggerType');
      }

      return Future.value(true);
    },
  );
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  static final LatLng _londonCityCenter = LatLng.degree(51.509865, -0.118092);

  static final List<LatLng> _timesSquarePolygon = [
    LatLng.degree(40.758078, -73.985640),
    LatLng.degree(40.757983, -73.985417),
    LatLng.degree(40.757881, -73.985493),
    LatLng.degree(40.757956, -73.985688),
  ];

  bool _hasServiceStarted = false;

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

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    await Permission.location.request();
    await Permission.locationAlways.request();
    await Permission.notification.request();

    _hasServiceStarted =
        await GeofenceForegroundService().startGeofencingService(
      contentTitle: 'Test app is running in the background',
      contentText:
          'Test app will be running to ensure seamless integration with ops team',
      notificationChannelId: 'com.app.geofencing_notifications_channel',
      serviceId: 525600,
      isInDebugMode: true,
      notificationIconData: const NotificationIconData(
        resType: ResourceType.mipmap,
        resPrefix: ResourcePrefix.ic,
        name: 'launcher',
      ),
      callbackDispatcher: callbackDispatcher,
    );

    log(_hasServiceStarted.toString(), name: 'hasServiceStarted');
  }

  Future<void> _createLondonGeofence() async {
    if (!_hasServiceStarted) {
      log('Service has not started yet', name: 'createGeofence');
      return;
    }

    await GeofenceForegroundService().addGeofenceZone(
      zone: Zone(
        id: 'zone#1_id',
        radius: 1000, // measured in meters
        coordinates: [_londonCityCenter],
        notificationResponsivenessMs: 15 * 1000, // 15 seconds
      ),
    );
  }

  Future<void> _createTimesSquarePolygonGeofence() async {
    if (!_hasServiceStarted) {
      log('Service has not started yet', name: 'createGeofence');
      return;
    }

    await GeofenceForegroundService().addGeofenceZone(
      zone: Zone(
        id: 'zone#2_id',
        radius: 10000, // measured in meters
        coordinates: _timesSquarePolygon,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                  onPressed: _createLondonGeofence,
                  child: const Text('Create Circular London Geofence')),
              const SizedBox(height: 30),
              ElevatedButton(
                  onPressed: _createTimesSquarePolygonGeofence,
                  child: const Text('Create Polygon Times Square Geofence')),
            ],
          ),
        ),
      ),
    );
  }
}

注意事项

  • 处理权限不是此包的一部分,请参考permission_handler插件来授予所需的权限(该插件也在示例中使用)

    • location
    • locationAlways
    • notification
  • callbackDispatcher方法将在与实际应用程序完全不同的隔离区中运行,因此如果您要在其中处理UI相关代码,则需要使用端口,更多信息请参阅这里

贡献指南

我们欢迎来自社区的贡献。如果您想为这个插件的发展做出贡献,请随时向我们的GitHub仓库提交PR。


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

1 回复

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


当然,以下是如何在Flutter项目中集成并使用geofence_foreground_service插件来实现地理围栏和前台服务的示例代码。

前提条件

  1. 确保你的Flutter环境已经正确配置。
  2. 在你的pubspec.yaml文件中添加geofence_foreground_service依赖:
dependencies:
  flutter:
    sdk: flutter
  geofence_foreground_service: ^最新版本号 # 请替换为最新版本号

配置Android权限

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

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

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

    <application
        ...>
        <!-- 添加服务声明 -->
        <service
            android:name=".GeofenceForegroundService"
            android:enabled="true"
            android:exported="false"
            android:foregroundServiceType="location" />

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

初始化插件和配置前台服务

在你的Flutter项目中,初始化geofence_foreground_service插件并配置前台服务。

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

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

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

class _MyAppState extends State<MyApp> {
  GeofenceForegroundService? _geofenceService;

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

  void _initializeGeofenceService() async {
    // 初始化插件
    _geofenceService = await GeofenceForegroundService.instance;

    // 配置前台服务通知
    Notification notification = Notification(
      title: "Geofence Service",
      text: "Running in the foreground",
      icon: "mipmap/ic_launcher", // 替换为你的应用图标资源
      smallIcon: "mipmap/ic_launcher_round", // 替换为你的应用小图标资源
      importance: NotificationImportance.HIGH,
      priority: NotificationPriority.HIGH,
      channelId: "geofence_channel",
      channelName: "Geofence Channel",
      channelDescription: "Channel for geofence notifications",
    );

    // 启动前台服务
    await _geofenceService!.startForegroundService(
      notification: notification,
      geofences: [
        Geofence(
          requestId: "geofence_1",
          latitude: 37.7749,
          longitude: -122.4194,
          radius: 1000.0, // 半径1公里
          transitionTypes: [
            GeofenceTransition.ENTER,
            GeofenceTransition.EXIT,
          ],
        ),
      ],
    );

    // 监听地理围栏事件
    _geofenceService!.geofenceEvents.listen((event) {
      print("Geofence event: ${event.requestId}, Transition: ${event.transitionType}");
    });
  }

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

处理权限请求

在实际应用中,你还需要处理权限请求,确保应用在运行时请求并获得了必要的权限。这里是一个简单的权限请求示例:

import 'package:permission_handler/permission_handler.dart';

Future<void> _requestPermissions() async {
  Map<Permission, PermissionStatus> statuses = await Permission.requestMultiple([
    Permission.location,
    Permission.activityRecognition,
  ]);

  // 处理权限请求结果
  statuses.forEach((permission, status) {
    if (status.isGranted) {
      print("${permission.toString()} permission granted.");
    } else if (status.isDenied) {
      print("${permission.toString()} permission denied.");
    } else if (status.isPermanentlyDenied) {
      print("${permission.toString()} permission permanently denied.");
      // 引导用户到设置页面手动开启权限
      openAppSettings();
    }
  });
}

initState方法中调用_requestPermissions()函数:

@override
void initState() {
  super.initState();
  _requestPermissions();
  _initializeGeofenceService();
}

注意事项

  1. 请确保你已经添加了permission_handler插件到你的pubspec.yaml中,用于处理权限请求。
  2. 前台服务通知的图标和资源需要放在Android项目的res/mipmap目录下。
  3. 这是一个基本的示例,实际项目中可能还需要处理更多的错误情况和边界情况。

希望这能帮助你在Flutter项目中成功集成并使用geofence_foreground_service插件。

回到顶部