oligazar
9/16/2019 - 7:29 AM

SwiftGpsServicePlugin

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()
}