Geolocation fails in Android 11 (API 30)

I’m having a hard time debugging this issue. I’m using Capacitor’s geolocation functionality (and have been for a while). I recently had to replace my laptop and now it seems something in my environment is preventing geolocation from working in the Android emulator, even though it works everywhere else, including the browser, iOS simulator, and iOS devices (including published apps in the App Store).

UPDATE: Also works on a physical Android device and API < 30. I’ve updated the title to indicate that this issue is specifically with Android API 30. This may be something that needs to be addressed in the Capacitor Geolocation plugin.

Here’s the log from Android Studio.

2021-03-29 12:49:52.632 9838-9838/org.myapp.myapp D/Capacitor: Registering plugin: Geolocation
2021-03-29 12:49:56.129 9838-9943/org.myapp.myapp V/Capacitor/Plugin: To native (Capacitor plugin): callbackId: 51862699, pluginId: Geolocation, methodName: getCurrentPosition
2021-03-29 12:49:56.129 9838-9943/org.myapp.myapp V/Capacitor: callback: 51862699, pluginId: Geolocation, methodName: getCurrentPosition, methodData: {}
2021-03-29 12:49:56.397 9838-9919/org.myapp.myapp D/Capacitor: Sending plugin error: {"save":false,"callbackId":"51862699","pluginId":"Geolocation","methodName":"getCurrentPosition","success":false,"error":{"message":"location unavailable"}}

Ionic Info:

Ionic:

   Ionic CLI                     : 6.13.1 (/usr/local/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 5.0.7
   @angular-devkit/build-angular : 0.803.26
   @angular-devkit/schematics    : 8.3.26
   @angular/cli                  : 8.3.26
   @ionic/angular-toolkit        : 2.2.0

Capacitor:

   Capacitor CLI   : 2.1.0
   @capacitor/core : 2.4.7

Utility:

   cordova-res : 0.15.3
   native-run  : not installed

System:

   NodeJS : v15.10.0 (/usr/local/Cellar/node/15.10.0_1/bin/node)
   npm    : 7.5.3
   OS     : macOS Big Sur

Capacitor Info:

  @capacitor/cli 2.1.0
  @capacitor/core 2.4.7
  @capacitor/android 2.4.7
  @capacitor/ios 2.4.7

Usage (Location Service):

import { Injectable } from "@angular/core";
import { ProjectConfigService } from "../project-config/project-config.service";
import * as geolib from "geolib"; // https://www.npmjs.com/package/geolib
import { Plugins } from "@capacitor/core";
const { Geolocation } = Plugins;

@Injectable({
  providedIn: "root"
})
export class LocationService {
  userLocation: any;
  nearby: any;
  northerly: any;
  nearbySortedEvent: any;
  locationAware: boolean;
  mapSettings = this.project.getMapSettings();

  constructor(private project: ProjectConfigService) {}

  isLocationAware(bool: boolean) {
    return new Promise(resolve => {
      this.locationAware = bool;
      //console.log('Location aware: '+this.locationAware);
      resolve(this.locationAware);
    });
  }

  sortNortherly(stories: any) {
    // used to control map marker z-axis stacking
    return new Promise(resolve => {
      let northerly = new Array();
      stories.forEach(function(story: any) {
        var distance = geolib.getDistance(
          { latitude: 90, longitude: 0 },
          { latitude: story.latitude, longitude: story.longitude },
          10
        );
        story.northerly = distance;
        northerly.push(story);
      });
      northerly.sort(function(a, b) {
        return a.northerly - b.northerly;
      });
      this.northerly = northerly;
      resolve(this.northerly);
    });
  }

  sortStoriesListNearby(stories: any, reverse = false) {
    // used for manual sort event on stories list
    return new Promise(resolve => {
      let displayUnit = this.mapSettings["distance_unit"];
      this.getUserLocation().then(userLocation => {
        if (userLocation !== false) {
          let nearbySorted = new Array();
          stories.forEach(function(story: any) {
            var distance = geolib.getDistance(
              { latitude: userLocation["lat"], longitude: userLocation["lon"] },
              { latitude: story.latitude, longitude: story.longitude },
              10
            );
            story.distance = []; // meters
            story.distance["meters"] = distance;
            story.distance["kilos"] =
              geolib.convertDistance(distance, "km").toFixed(2) + " km";
            story.distance["miles"] =
              geolib.convertDistance(distance, "mi").toFixed(2) + " mi";
            story.distance["display"] = story.distance[displayUnit];
            nearbySorted.push(story);
          });
          if (!reverse) {
            // closest
            nearbySorted.sort(function(a, b) {
              return a.distance.meters - b.distance.meters;
            });
          } else {
            // furthest
            nearbySorted.sort(function(a, b) {
              return b.distance.meters - a.distance.meters;
            });
          }

          this.nearbySortedEvent = nearbySorted;
          resolve(this.nearbySortedEvent);
        } else {
          //console.log('something went wrong...'); // @TODO: add toast
          this.nearbySortedEvent = stories;
          resolve(this.nearbySortedEvent);
        }
      });
    });
  }

  sortByProximity(stories: any, n: number) {
    // used to sort by location on startup/refresh for discover view
    return new Promise(resolve => {
      let displayUnit = this.mapSettings["distance_unit"];
      this.getUserLocation().then(userLocation => {
        if (userLocation !== false) {
          let nearby = new Array();
          stories.forEach(function(story: any) {
            var distance = geolib.getDistance(
              { latitude: userLocation["lat"], longitude: userLocation["lon"] },
              { latitude: story.latitude, longitude: story.longitude },
              10
            );
            story.distance = []; // meters
            story.distance["meters"] = distance;
            story.distance["kilos"] =
              geolib.convertDistance(distance, "km").toFixed(2) + " km";
            story.distance["miles"] =
              geolib.convertDistance(distance, "mi").toFixed(2) + " mi";
            story.distance["display"] = story.distance[displayUnit];
            nearby.push(story);
          });
          nearby.sort(function(a, b) {
            return a.distance.meters - b.distance.meters;
          });
          if (n !== 0) this.nearby = nearby.slice(0, n);
          resolve(this.nearby);
        } else {
          this.nearby = false;
          resolve(this.nearby);
        }
      });
    });
  }

  getUserLocation() {
    return new Promise(resolve => {
      Geolocation.getCurrentPosition().then(
        position => {
          this.userLocation = {
            lat: position.coords.latitude,
            lon: position.coords.longitude
          };
          this.isLocationAware(true);
          resolve(this.userLocation);
        },
        _err => {
          this.isLocationAware(false);
          this.userLocation = false;
          resolve(this.userLocation);
        }
      );
    });
  }
}

Permissions:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />

Okay, so apparently it works on some emulated devices. I was testing against API 30, which does not work. But it does work with API 28 for example.

So maybe someone can explain to me why emulating API 30 causes it to fail?

From the Android docs:

“Caution: If your app targets Android 11 (API level 30) or higher, the system enforces this best practice [asking for foreground and background access in separate requests]. If you request a foreground location permission and the background location permission at the same time, the system ignores the request and doesn’t grant your app either permission.” – Source

I don’t need or want background access, so I’m wondering how to fix this so that my request is only for while the app is in use.

Capacitor’s Geolocation plugin uses location services library, so if the emulator doesn’t have google services it will fail.
When you create a virtual device make sure the image has “Google APIs” or “Google Play” in the image name.

Thanks for the clarification, I appreciate it!

do i need to install this on my mobile device? I have thesame issue running on android 11

There has been a bug in latest “google play services” package (version 21.30.16) that makes the geolocation plugin to not work.
We are working in a fix.

Hi sirr, I have noticed that on android phone, geolocation wont work it give me a “location unavailable” error. Is there a work around for this? I really need this to work on my app

If you have google play services package version 21.30.16 installed, uninstall it. Other than that, you’ll have to wait for next release of @capacitor/camera plugin

is there a specific google play services version, that i need to install, In order for the capacitor geolocation to work?

Latest version is 21.30.16 at the moment and that’s the one with the problem, so if you uninstall it you’ll get the one that was the default in your device, any version different from 21.30.16 should work.

Can we expect a fix for @capacitor version 2? Or is it going to be fixed only for version 3? Also, this bug exist for months, it’s not something new, users are really mad using my application. The thing is that calling Plugins.Geolocation.getCurrentPosition throws an exception right away, one second later location is found, but that’s too late for me. I mean I can’t tell all my users to downgrade their Google Play services. And no, this topic is not resolved. Please reopen it and keep us updated about the progress of fixing this issue.

No, there won’t be a fix for Capacitor 2 because it requires a breaking change, so can’t be done in Capacitor 2. Breaking changes require major version bumps, so it would mean we would need to create Capacitor 3, but Capacitor 3 already exists. That’s the reason we moved plugins out in Capacitor 3, so we could add breaking changes faster if something affected the plugin code. In this case, we will need to release @capacitor/geolocation 2.0.0

This is related to the 21.30.16 update, if you had an issue with geolocation before, then it’s a different issue. It was working fine in all my devices until I updated. I’ve reported it to google and they are working on a fix.

Yes, I agree it worked before, it worked on my device too, but some devices had an occasional issue from time to time, so it’s not a code related, it’s somehow device and capacitor related. Do you suggest to update capacitor from 2 to 3 now? Thanks.

Is there any workaround for this issue in capacitor 2, we can’t update to capacitor 3 currently. I can uninstall the play service in my device, but it is not a viable solution when deploying to the store. we at least need a temporary solution, so that we can migrate to capacitor 3 on our own pace

Open Android Studio and in Geolocation.java file replace body for method requestLocationUpdates
the code is here Sign in - Google Accounts

This is only workaround and if you rebuild plugin or change version the code will be removed. But it is enough to build your app.
I did it and for this moment it works for my app.

Same issue… Temporarily solved by commenting out these lines in Geolocation.java

if (!availability.isLocationAvailable()) {
call.error(“location unavailable”);
clearLocationUpdates();
}

What would the timeline be for having this fix for Capacitor 2 released from this PR https://github.com/ionic-team/capacitor/issues/4962#issuecomment-912492540 deployed?

We followed these steps to patch and had to add an optional step 6 to have it run in Ionic AppFlow:

  1. Access @capacitor/android and update Geolocation.java using this PRs changes (https://github.com/ionic-team/capacitor/pull/4992/files)
  2. Run npx patch-package @capacitor/android
  3. Update package.json with "postinstall": "patch-package"
  4. npm install patch-package so dependency exists when npm install is run during Ionic AppFlow
  5. Commit the patch file and changes to package.json
  6. (Optional) If you get an error like “cannot run in wd […]” add a .npmrc with unsafe-perm = true when using Ionic AppFlow since we can’t use a non-root user

References:

It has been released on Capacitor 2.5.0, but please, update to Capacitor 3, we released it 4 months ago.

Capacitor 2 will probably only receive security updates in the future, but most likely not bug fixes.

1 Like

Thanks for the reply. With that patched and deployed out in our app. I’ll start looking at upgrading to Capacitor 3.x :slight_smile: