GeolocatorPlugin icon indicating copy to clipboard operation
GeolocatorPlugin copied to clipboard

GetPositionAsync never returns, timesout or raises exception on first call

Open downsider7 opened this issue 6 years ago • 22 comments

Bug Information

Version Number of Plugin: 4.2.0.0 Device Tested On: iPhone 5S iOS11.3 Simulator Tested On: N/A Version of VS: 2017 Version of Xamarin: 2.3.4.267 Versions of other things you are using: Xcode 9.3

Steps to reproduce the behaviour

Run the application for the first time under debug. Request position using .GetPositionAsync() Confirm the pop-up to allow permission for locations .GetPositionAsync() never returns, timesout, or throws an exception. Close app, launch again, no need for pop-up as permissions already granted, works fine

Expected Behavior

GetPositionAsync() should either turn a location, timeout or throw an exception. Have tried adding a timeout of 10s (Timespan) to the .GetPositionAsync() call and/or a cancellation token - still never returns

Actual Behavior

.GetPositionAsync() never returns

Code snippet

private async Task<bool> GetCurrentLocation()
        {
            var locator = CrossGeolocator.Current;
            locator.DesiredAccuracy = 100;
            if (!CrossGeolocator.IsSupported)
                return false;
            if (!CrossGeolocator.Current.IsGeolocationAvailable)
                return false;
            try
            {
                var position = await locator.GetPositionAsync(); // This never returns

                latitude = position.Latitude;
                longitude = position.Longitude;
                return true;
            }
            catch (Exception e)
            {
                string reason = e.Message;
                return false;
            }
        }

plist <key>NSLocationWhenInUseUsageDescription</key> <string>You are about to use location!</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>You are about to use location!</string> <key>UIBackgroundModes</key> <array> <string>location</string> <string>bluetooth-central</string> </array>

Screenshots

N/A

downsider7 avatar Apr 09 '18 14:04 downsider7

im getting exactly the same issue

stevehawkins avatar Apr 30 '18 22:04 stevehawkins

Have you read through: https://jamesmontemagno.github.io/GeolocatorPlugin/GettingStarted.html specifically the iOS section on proper plist setup.

jamesmontemagno avatar Apr 30 '18 22:04 jamesmontemagno

Hi, Yes, read, followed to the letter. It still hangs. G.

downsider7 avatar Apr 30 '18 22:04 downsider7

I can confirm that issue as well.

ghost avatar May 22 '18 11:05 ghost

I can confirm as well. Seems to be an issue building with 11.3 SDK. Was working fine when I was building with SDK 10.2. However, now that I upgraded my Mac(build machine) to Sierra High (Mac OS 10.13). I cannot go back to 10.2 SDK. Well, I can install the SDK but get several build issues. Not important in this case though. Just know despite not being able to deploy to a device(Ad-Hoc) under 10.2. I can use the simulator and the response from the call is almost immediate. So it does work when NOT using 11.3 SDK. About to test with 11.0. However, I can assume it will not work though. Appears to be the changes to 11+. Possibly permissions. Just as stevenhawkin I followed the permission document correctly. Will let you know my findings after I try building with SDK 11. I think the difference between the original poster and myself is that I actually reach the time out. I have set it to a minute before and never was able to get the geo location when built under SDK 11.3.

ehmason avatar May 29 '18 21:05 ehmason

I also have this one on an app/code that worked fine in the past.

dinisvieira-xo avatar Jun 04 '18 20:06 dinisvieira-xo

I was getting the same issue with below code. position= await locator.GetPositionAsync(TimeSpan.FromTicks(Constants.GPS_TIME_OUT)); Then I changed the code like this. position= await locator.GetPositionAsync(TimeSpan.FromSeconds(Constants.GPS_TIME_OUT));

Now I get the position without any issues. This may help someone.

devapalanisamy avatar Jul 20 '18 12:07 devapalanisamy

@devapalanisamy Thanks for the suggestions, doesn't seem to fix it for me though :/

dinisvieira-xo avatar Jul 20 '18 13:07 dinisvieira-xo

Your info plist looks inforrect.

Simply Add:

NSLocationWhenInUseUsageDescription This app needs access to location when open.

Background Updates Only implement this and add these properites if you need background updates for your application. Most likely you will not. Adding this also has direct impact on permissions and prompts to the user. Please be very careful when adding this information.

Inside of your info.plist you must enable Background Modes/UIBackgroundModes for location updates. Here is a full guide. Your info.plist should contain something like this:

UIBackgroundModes location

In addition to NSLocationWhenInUseUsageDescription you are required to add NSLocationAlwaysAndWhenInUseUsageDescription keys in your app’s Info.plist file. (If your app supports iOS 10 and earlier, the NSLocationAlwaysUsageDescription key is also required.) If those keys are not present, authorization requests fail immediately.

NSLocationAlwaysUsageDescription This app needs access to location when in the background. NSLocationAlwaysAndWhenInUseUsageDescription This app needs access to location when open and in the background.

jamesmontemagno avatar Jul 20 '18 15:07 jamesmontemagno

Also, if you are using the simulator make sure you actually set the location in it first

If you are only using it while your app is running ONLY add the first permission.

jamesmontemagno avatar Jul 20 '18 15:07 jamesmontemagno

I was facing the same issue for some Android device. Working Device = Samsung, Not Working Device = Motorola(MotoG)

Below is my code.

var locator = CrossGeolocator.Current; locator.DesiredAccuracy = 10; var _position = await locator.GetPositionAsync(TimeSpan.FromSeconds(10));

ErBhautik avatar Aug 13 '18 09:08 ErBhautik

I had the same problem. I was using xamarin live player and doesnt works. var locator = CrossGeolocator.Current; locator.DesiredAccuracy = 50; var _position = await locator.GetPositionAsync(TimeSpan.FromSeconds(60));

marceloraposo avatar Aug 13 '18 21:08 marceloraposo

Same problem here with android, using live player, android emulator or real device, it always crashs and 2 days before everything was working fine.

ederjbezerra avatar Oct 20 '18 18:10 ederjbezerra

I was facing the same issue on Android, but it was caused my not awaiting an interface call to the method calling: var locator = CrossGeolocator.Current;

Onjay avatar Dec 10 '18 00:12 Onjay

Any resolution for this? I am also facing this issue and it was working, now the call doesn't return

brikesh987 avatar Jan 06 '19 04:01 brikesh987

Same here, tested with:

  • iPhone 5S iOS 12.1
  • Plugin.Geolocator 4.5.0.6
  • Xamarin.iOS Version: 12.0.0.15 (Visual Studio Community)
  • Mac OS X 10.14.1

nobiGT avatar Jan 17 '19 03:01 nobiGT

Having the same issue using GetLocationAsync() from Xamarin.Essentials too.

Actually it happens intermittently like once in 10 times, at least on iPhone 5S in debug mode. I verified this repeating the following: Install app -> Launch -> Tap "Allow" in the permission dialog -> Stuck at GetPositionAsync (or GetLocationAsync if using Xamarin.Essentials)) -> Remove app -> Install app -> ...

nobiGT avatar Jan 17 '19 04:01 nobiGT

Do you have code samples as to how you are calling this?

jamesmontemagno avatar Jan 17 '19 06:01 jamesmontemagno

Hey, Sorry I never came back to this. I completely changed the code we had. It would take a while to explain all what I changed. So I will just post my code as it is today that works explaining it the best I can. All within the App.xaml.cs class.

protected override async void OnStart() {
    await StartGeoTracking(true);
}

private async void Position_Changed(object sender, PositionEventArgs e) {
    //Do whatever you need to do with the location once you have it.  Note at the time I implemented this code iOS would ping the location too often.  The first parameter of StartListeningAsync (minTime) was ignored on iOS.  So within this function we set up a private property that would hold the last time we successfully got a location.  Had a constant which we held the minimum wait for us and if the time between last time we got the location was not greater than last time + our minimum wait then just return and do nothing with the geo location.
}

private async Task StartGeoTracking(bool isStartUp = false) {
    if (_locator == null) {
        _locator = CrossGeolocator.Current;
    }

    //No need to do anything if
    //1. Already listening
    //2. Not first time in the application. Is not enabled by device (user opted out)
    if (_locator.IsListening || (!isStartUp && !_locator.IsGeolocationEnabled)) {
        return;
    }

    try {
        _locator.DesiredAccuracy = 1;
        _locator.AllowBackgroundUpdates = true;
        _locator.PositionChanged += PositionChanged;

        var status = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.Location);
        if (status != PermissionStatus.Granted) {
            if (await CrossPermissions.Current.ShouldShowRequestPermissionRationaleAsync(Permission.Location)) {
                Debug.WriteLine("Need location");
            }

            var results = await CrossPermissions.Current.RequestPermissionsAsync(Permission.Location);
            //Best practice to always check that the key exists
            if (results.ContainsKey(Permission.Location))
                status = results[Permission.Location];
        }

        if (status == PermissionStatus.Granted) {
            if (Xamarin.Forms.Device.RuntimePlatform == Xamarin.Forms.Device.Android) {
                // android at least every 1 minute or max of 20 meters before update.
                await _locator.StartListeningAsync(1000 * 60, 20);
            } else {
                // on other platforms, specifically iOS the time is ignored, we need to listen based on distance. 20 meters
                await _locator.StartListeningAsync(1, 20);
            }
        }
    } catch (Exception) {
        //Whatever you need to do
    }
}

public async Task StopGeoTracking() {
    if (_locator == null) {
        return;
    }

    if (_locator.IsListening) {
        await _locator.StopListeningAsync();
    }
}

ehmason avatar Jan 17 '19 19:01 ehmason

After reading over my comments and adding to this. Make sure you have the permissions set up for location usage. Also, from Onjay's comment it is important the entire context is asynchronous. From my last comment it is important to check for IsGeolocationEnabled with checking that it is not the first time the application is running through that code because IsGeolocationEnabled will be false on the first run through. See the code for the rest. Hope this helps.

ehmason avatar Jan 17 '19 19:01 ehmason

Actually it happens intermittently like once in 10 times

I figured out this problem of GetPositionAsync stuck forever in my case. In short, this is a rare event that happens when CheckPermissionStatusAsync is running on UI thread (OnStart in my case) and GetPositionAsync running on another thread (as a Task) at the same time.

Here is how it happens:

  1. At OnStart I call CheckPermissionStatusAsync and RequestPermissionsAsync
  2. Then somehow OnSleep is called when opening the permission dialog and OnResume called after closing the dialog
  • This seems counterintuitive (I was not aware of this) and is the culpit of the problem but that's how Xamarin.iOS works as stated here: https://github.com/smstuebe/xamarin-fingerprint/issues/94
  • At OnResume I call CheckPermissionStatusAsync again to 100% make sure GPS is available, after checking other things like server connection
  1. After closing the dialog with user's "Allow", I call GetPositionAsync as a part of Task.Run() at MainPage()
  2. Then sometimes, with some bad luck, CheckPermissionStatusAsync running from OnResume after closing the dialog and GetPositionAsync from Task.Run() hit at the same time and GetPositionAsync gets stuck without throwing any exception.

This can be easily reproduced running CheckPermissionStatusAsync in an infinite loop on UI thread (like OnStart or OnResume) while running GetPositionAsync as a Task somewhere as well in an infinite loop so they hit each other at one point of time for sure.

As a workaround I am thinking to remove or move the GPS permission check at OnResume to somewhere else. Hope this helps.

nobiGT avatar Jan 17 '19 22:01 nobiGT

I'm also facing this issue right now. Was anything done to address it?

EDIT: On my case the method call usually works (all Info.plist settings are fine), but it occasionally gets stuck on this method. Did anyone find a workaround for this issue?

fl-eric avatar Dec 26 '19 22:12 fl-eric