Search code examples
iosswiftfluttercore-location

Cannot get location from swift to flutter using custom platform-specific code


1. I have to get location of iOS device, but I am not using flutter packages. I need to get it from swift code and pass it to flutter.

I don't have any experience in swift. I have used the following code yet. Can someone please help.

import UIKit
import Flutter
import CoreLocation

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        let testChannel = FlutterMethodChannel(name: "com.example.testFlutter/test",
                                               binaryMessenger: controller.binaryMessenger)
        testChannel.setMethodCallHandler({
            [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            // This method is invoked on the UI thread.
            if call.method == "getLocation"{
                self?.getLocation(result: result)
            }
            else{
                result(FlutterMethodNotImplemented)
                return
            }
        })
        
        
        
        
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    private func getLocation(result: FlutterResult){
        var locationManager: CLLocationManager?
        locationManager?.requestAlwaysAuthorization()
        if CLLocationManager.locationServicesEnabled() {
            locationManager?.startUpdatingLocation()
            result("updating location")
        }
        
        
        
    }
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation], result: FlutterResult) {
        result("testing")
        guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else { return }
        result("location = \(locValue.latitude) \(locValue.longitude)")
    }
}

2. Code on the Flutter side is

static const platform = MethodChannel('com.example.testFlutter/test');
printMsgFromiOS() async {
    try {
      final String result = await platform.invokeMethod('getLocation');
      print(result);
    } on PlatformException catch (e) {
      print(e.message);
    }
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    printMsgFromiOS();
  }

OUTPUT While running the code, it is error free and im getting the String as an output. The String is "updating location", which is the statement in getLocation() function of Swift code.


Solution

  • PART A - ONE TIME LOCATION

    I am providing an answer to my question which gives me one time location from Swift to Flutter using following code. However, I am attempting to find a solution to get updates of the location every time it changes, and I will post that if successful.

    (Update - solution for getting location multiple times is available in Part B below).

    1. Updated Swift Code as compared to the one in question

    
    import UIKit
    import Flutter
    import CoreLocation
    
    @UIApplicationMain
    @objc class AppDelegate: FlutterAppDelegate {
        override func application(
            _ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
        ) -> Bool {
            let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
            let testChannel = FlutterMethodChannel(name: "com.example.testFlutter/test",
                                                   binaryMessenger: controller.binaryMessenger)
            testChannel.setMethodCallHandler({
                [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
                // This method is invoked on the UI thread.
                if call.method == "getLocation"{
                    self?.getLocation(result: result)
                }
                else{
                    result(FlutterMethodNotImplemented)
                    return
                }
            })
            
            
            
            
            GeneratedPluginRegistrant.register(with: self)
            return super.application(application, didFinishLaunchingWithOptions: launchOptions)
        }
        
        private func getLocation(result: FlutterResult){
            let locationManager = CLLocationManager()
            
            locationManager.requestWhenInUseAuthorization()
            
            if let location = locationManager.location?.coordinate {
                let string = "\(location.latitude), \(location.longitude)"
                
                result(string)
            } else {
                // Handle error
                result("Error")
            }
        }
    }
    
    

    2. Flutter code remains the same as in question

    3. OUTPUT

    enter image description here



    PART B - MULTIPLE TIMES LOCATION

    EventChannel is used in the following code to obtain location multiple times from swift. I have attempted to use delete, however, that didn't work and I'm still working on it. For now, I have settled on using a timer.

    1. Swift Code

    import UIKit
    import Flutter
    import CoreLocation
    
    @UIApplicationMain
    @objc class AppDelegate: FlutterAppDelegate {
        override func application(
            _ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
        ) -> Bool {
            let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
            let testChannel = FlutterEventChannel(name: "com.example.testFlutter/test",
                                                  binaryMessenger: controller.binaryMessenger)
            let swiftTestChannel = SwiftTestChannel()
            swiftTestChannel.setup(flutterViewController: controller)
            testChannel.setStreamHandler(swiftTestChannel)
    
            GeneratedPluginRegistrant.register(with: self)
            return super.application(application, didFinishLaunchingWithOptions: launchOptions)
        }
      
    }
    class SwiftTestChannel: NSObject, FlutterStreamHandler, CLLocationManagerDelegate {
        var eventSink: FlutterEventSink?
        
        private let channelName = "mychannel"
        
        func setup(flutterViewController: FlutterViewController) {
            let channel = FlutterEventChannel(name: channelName, binaryMessenger: flutterViewController as! FlutterBinaryMessenger)
            channel.setStreamHandler(self)
        }
        
        func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
            eventSink = events
            let timer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { _ in
                //            self.eventSink?("My string from Swift")
                let locationManager = CLLocationManager()
                
                locationManager.requestWhenInUseAuthorization()
                
                if let location = locationManager.location?.coordinate {
                    let string = "\(location.latitude), \(location.longitude)"
                    
                    self.eventSink?(string)
                } else {
                    // Handle error
                    self.eventSink?("Error")
                }
            }
            //        getLocation()
            return nil
        }
        
        func onCancel(withArguments arguments: Any?) -> FlutterError? {
            return nil
        }
    }
    
    

    2. Flutter Code

      static const platform = EventChannel('com.example.testFlutter/test');
      printMsgFromiOS() async {
        try {
          platform.receiveBroadcastStream().listen((data) {
            print(data.toString());
          });
        } on PlatformException catch (e) {
          print(e.message);
        }
      }
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        printMsgFromiOS();
      }
    

    3. OUTPUT

    enter image description here