SwiftGpsServicePlugin
import UIKit
import Flutter
import GoogleMaps
import Fabric
import Crashlytics
import gps_service_plugin
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Register the plugins with the AppDelegate
registerPlugins(self)
// Set registerPlugins as a callback within GeofencingPlugin. This allows
// for the Geofencing plugin to register the plugins with the background
// FlutterEngine instance created to handle events. If this step is skipped,
// other plugins will not work in the geofencing callbacks!
SwiftGpsServicePlugin.setPluginRegistrantCallback(registerPlugins)
// Initialize crashlytics
Fabric.with([Crashlytics.self])
// Sut Google Maps api key
GMSServices.provideAPIKey("AIzaSyBCk1oweYbhQb2BqK3vRq1_F0mHl29YLi0")
// Override point for customization after application launch.
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
func registerPlugins(_ registry: FlutterPluginRegistry) {
GeneratedPluginRegistrant.register(with: registry)
}
import Flutter
import UIKit
import CoreLocation
public class SwiftGpsServicePlugin: NSObject, FlutterPlugin {
static var instance: SwiftGpsServicePlugin?
static var registerPlugins: FlutterPluginRegistrantCallback?
var initialized = false
let eventTypePosition = 0
let eventTypeLyfecycle = 1
let keyCallbackHandle = "callback_handle"
let keyCallbackDispatcherHandle = "callback_dispatcher_handle"
let keyIsTrecking = "is_trecking"
var _eventQueue: NSMutableArray!
var _persistentState: UserDefaults!
var _locationManager: CLLocationManager!
var _headlessRunner: FlutterEngine!
var _registrar: FlutterPluginRegistrar!
var _mainChannel: FlutterMethodChannel!
var _backgroundChannel: FlutterMethodChannel!
public static func setPluginRegistrantCallback(_ callback: @escaping FlutterPluginRegistrantCallback) {
registerPlugins = callback
}
init(_ registrar: FlutterPluginRegistrar) {
super.init()
// 1. Retrieve NSUserDefaults which will be used to store callback handles
// between launches.
_persistentState = UserDefaults.standard
_eventQueue = NSMutableArray()
// 2. Initialize the location manager, and register as its delegate.
_locationManager = CLLocationManager.init()
_locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
_locationManager.distanceFilter = 50
_locationManager.delegate = self
_locationManager.requestAlwaysAuthorization()
_locationManager.allowsBackgroundLocationUpdates = true
_locationManager.pausesLocationUpdatesAutomatically = true // This setup pauses location manager if location wasn't changed
_locationManager.allowsBackgroundLocationUpdates = true // For iOS9 we have to call this method if we want to receive location updates in background mode
// 3. Initialize the Dart runner which will be used to run the callback
// dispatcher.
_headlessRunner = FlutterEngine.init(name: "GpsServiceIsolate", project: nil, allowHeadlessExecution: true)
_registrar = registrar
// 4. Create the method channel used by the Dart interface to invoke
// methods and register to listen for method calls.
_mainChannel = FlutterMethodChannel(name: "us.kostenko/gps_plugin", binaryMessenger: registrar.messenger())
registrar.addMethodCallDelegate(self, channel: _mainChannel)
// 5. Create a second method channel to be used to communicate with the
// callback dispatcher. This channel will be registered to listen for
// method calls once the callback dispatcher is started.
_backgroundChannel = FlutterMethodChannel(name: "us.kostenko/gps_plugin_background", binaryMessenger: _headlessRunner as! FlutterBinaryMessenger)
}
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [AnyHashable : Any] = [:]) -> Bool {
// Check to see if we're being launched due to a location event.
if (launchOptions[UIApplication.LaunchOptionsKey.location] != nil) {
// Restart the headless service.
self.startLocationService(self.getHandle(forKey: keyCallbackDispatcherHandle))
}
// Note: if we return false, this vetos the launch of the application.
return true
}
public static func register(with registrar: FlutterPluginRegistrar) {
if (instance == nil) {
let instance = SwiftGpsServicePlugin(registrar)
registrar.addApplicationDelegate(instance)
}
}
private func startLocationService(_ callbackDispatcherHandle: Int64) {
guard let info: FlutterCallbackInformation = FlutterCallbackCache
.lookupCallbackInformation(callbackDispatcherHandle) else {
print("failed to find callback"); return
}
let entrypoint = info.callbackName
let uri = info.callbackLibraryPath
_headlessRunner.run(withEntrypoint: entrypoint, libraryURI: uri)
// Once our headless runner has been started, we need to register the application's plugins
// with the runner in order for them to work on the background isolate. `registerPlugins` is
// a callback set from AppDelegate.m in the main application. This callback should register
// all relevant plugins (excluding those which require UI).
guard let registerPlugins = SwiftGpsServicePlugin.registerPlugins else {
print("failed to set registerPlugins"); return
}
registerPlugins(_headlessRunner)
_registrar.addMethodCallDelegate(self, channel:_backgroundChannel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "GpsManager.initializeService": initializeService(call, result)
case "LocationService.initialized": initialized(result)
case "GpsManager.startListeningLocation": startListeningLocation(result)
case "GpsManager.stopListeningLocation": stopListeningLocation(result)
case "GpsManager.isTreckingLocation": isTreckingLocation(result)
case "GpsManager.dispose": dispose(result)
default: result(FlutterMethodNotImplemented)
}
}
private func initializeService(_ call: FlutterMethodCall, _ result: FlutterResult) {
print("initializeService")
let map = call.arguments as! NSDictionary
guard
let callbackDispatcherHandle = map["callbackDispatcherHandle"] as? Int64,
let callbackHandle = map["callbackHandle"] as? Int64
else {return}
saveHandle(callbackDispatcherHandle, forKey: keyCallbackDispatcherHandle)
saveHandle(callbackHandle, forKey: keyCallbackHandle)
self.startLocationService(callbackDispatcherHandle)
result(true)
}
private func initialized(_ result: FlutterResult) {
synchronized(self) {
self.initialized = true
// Send the geofence events that occurred while the background
// isolate was initializing.
while (_eventQueue.count > 0) {
let updateMap = _eventQueue[0] as! [String : Any]
_eventQueue.removeObject(at: 0)
sendEvent(updateMap)
}
}
result(nil);
}
private func startListeningLocation(_ result: FlutterResult) {
print("startListeningLocation")
saveIsTrecking(true)
_locationManager.startUpdatingLocation()
self.sendLifecycleEvent("startTrecking")
result(true)
}
private func stopListeningLocation(_ result: FlutterResult) {
print("stopListeningLocation")
saveIsTrecking(false)
_locationManager.stopUpdatingLocation()
self.sendLifecycleEvent("stopTrecking")
result(true)
}
private func isTreckingLocation(_ result: FlutterResult) {
result(isTrecking())
}
private func dispose(_ result: FlutterResult) {
// NOOP?
}
}
extension SwiftGpsServicePlugin: CLLocationManagerDelegate {
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("locationManager, location: \(locations.last!)")
let updateMap = prepareUpdateMap(position: locations.last!)
synchronized(self) {
if (initialized) {
self.sendEvent(updateMap)
} else {
_eventQueue.add(updateMap)
}
}
}
private func sendEvent(_ updateMap: [String : Any]) {
_backgroundChannel.invokeMethod("", arguments: updateMap)
}
private func sendLifecycleEvent(_ event: String) {
let lifeCycleUpdateMap = [
"type" : eventTypeLyfecycle,
"callbackHandle" : getHandle(forKey: keyCallbackHandle),
"event" : event
] as [String : Any]
sendEvent(lifeCycleUpdateMap)
}
private func prepareUpdateMap(position: CLLocation) -> [String : Any] {
let locationMap = [
"time" : formatDate(position.timestamp), // string
"latitude" : position.coordinate.latitude, // double
"longitude" : position.coordinate.longitude, // double
"accuracy" : position.horizontalAccuracy, // float
"altitude" : position.altitude, // double
"speed" : position.speed // float
] as [String : Any]
let updateMap = [
"type" : eventTypePosition,
"callbackHandle" : getHandle(forKey: keyCallbackHandle), // string
"locationMap" : locationMap // double
] as [String : Any]
print("prepareUpdateMap: \(updateMap)")
return updateMap
}
private func formatDate(_ timestamp: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
dateFormatter.locale = Locale.current
return dateFormatter.string(from: timestamp)
}
}
// persistance state
extension SwiftGpsServicePlugin {
private func saveHandle(_ handle: Int64, forKey key: String) {
_persistentState.set(handle, forKey: key)
}
private func getHandle(forKey key: String) -> Int64 {
return _persistentState.object(forKey: key) as? Int64 ?? 0
}
private func isTrecking() -> Bool {
return _persistentState.bool(forKey: keyIsTrecking)
}
private func saveIsTrecking(_ value: Bool) {
return _persistentState.set(value, forKey: keyIsTrecking)
}
}
public func synchronized<T>(_ lock: AnyObject, body: () throws -> T) rethrows -> T {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return try body()
}