Created
September 16, 2019 07:29
-
-
Save oligazar/96f5315df1e0a22b6cc9a6f9747859ee to your computer and use it in GitHub Desktop.
SwiftGpsServicePlugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment