react-native-geolocation
react-native-geolocation copied to clipboard
IOS requestAuthorization doesn't return success or error until you go into the settings and switch the permission value
Environment
My machine (windows, build on a mac mini):
System:
OS: Windows 10 10.0.19045
CPU: (4) x64 Intel(R) Core(TM) i5-7600 CPU @ 3.50GHz
Memory: 4.25 GB / 15.94 GB
Binaries:
Node:
version: 18.14.2
path: C:\Program Files\nodejs_v18\node.EXE
Yarn:
version: 1.22.10
path: ~\AppData\Roaming\npm\yarn.CMD
npm:
version: 9.5.0
path: C:\Program Files\nodejs_v18\npm.CMD
Watchman: Not Found
SDKs:
Android SDK: Not Found
Windows SDK: Not Found
IDEs:
Android Studio: Not Found
Visual Studio:
- 17.10.35122.118 (Visual Studio Community 2022)
- 16.11.33529.622 (Visual Studio Community 2019)
Languages:
Java:
version: 17.0.12
path: /c/Program Files/Microsoft/jdk-17.0.12.7-hotspot/bin/javac
Ruby: Not Found
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 18.3.1
wanted: 18.3.1
react-native:
installed: 0.75.3
wanted: 0.75.3
react-native-windows: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: false
iOS:
hermesEnabled: Not found
newArchEnabled: Not found
The remote server:
System:
OS: Linux 5.4 Ubuntu 20.04.6 LTS (Focal Fossa)
CPU: (2) x64 Intel(R) Xeon(R) CPU E5-2687W v4 @ 3.00GHz
Memory: 1.83 GB / 3.83 GB
Shell: 5.0.17 - /bin/bash
Binaries:
Node: 18.20.4 - ~/.nvm/versions/node/v18.20.4/bin/node
Yarn: Not Found
npm: 10.7.0 - ~/.nvm/versions/node/v18.20.4/bin/npm
Watchman: Not Found
SDKs:
Android SDK: Not Found
IDEs:
Android Studio: Not Found
Languages:
Java: Not Found
npmPackages:
@react-native-community/cli: Not Found
react: 18.3.1 => 18.3.1
react-native: 0.75.3 => 0.75.3
npmGlobalPackages:
*react-native*: Not Found
Platforms
iOS
Versions
- Android: -
- iOS: 17.6.1
- react-native-geolocation: 3.4.0
- react-native: 0.75.3
- react: 13.3.1
Description
On iOS the .requestAuthorization seems to do nothing until you go into the settings and switch the location permission then suddenly all pending requests are returned in success/error. Android required me to set locationProvider to playServices but no matter what I try for the iOS configurations I can't get it to call a function normally.
Reproducible Demo
This is the exact code i use in our project and in a clean project. (with moment.js installed for timing)
Info.plist has this key NSLocationWhenInUseUsageDescription but no value
// this seems to work fine on android
Geolocation.setRNConfiguration({
locationProvider: 'playServices',
authorizationLevel: 'whenInUse'
});
let moment_object = moment();
// this gets called
console.log('Geolocation.requestAuthorization', Geolocation.requestAuthorization, moment_object.format('HH:mm:ss'));
// success or error only gets called when you go into the iOS settings and switch the location permissions
// otherwise this just hangs in limbo
Geolocation.requestAuthorization(() => { // success callback
console.log('Geolocation.requestAuthorization', true, moment_object.format('HH:mm:ss'), moment().format('HH:mm:ss'));
}, () => { // error callback
console.log('Geolocation.requestAuthorization', false, moment_object.format('HH:mm:ss'), moment().format('HH:mm:ss'));
});
Don't use that for checking the permission, use this instead:
package:https://www.npmjs.com/package/react-native-permissions
export const requestAuthorization = async (): Promise<boolean> => {
const permission =
Platform.OS === 'android'
? PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION
: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE;
try {
const result = await check(permission);
if (result === RESULTS.GRANTED) {
return true;
} else if (result === RESULTS.DENIED || result === RESULTS.BLOCKED) {
const requestResult = await request(permission);
return requestResult === RESULTS.GRANTED;
} else {
return false;
}
} catch (err) {
console.warn(err);
return false;
}
};
The issue above still exists, I just runned into it today. Same as described, it looks like Geolocation.requestAuthorization doesn't call any of its callbacks on iOS.
Even if @stealkiller06 solution works, I don't think that "use another package to do it" is a valid solution. It just adds an extra redundent dependency instead of fixing the real issue.. If this method exists, it must be usable (and used).
EDIT: Explanation found
After digging up a little bit in the module iOS native code, if found this :
In RNCGeolocation.mm, line 284 :
// Request location access permission
if (wantsAlways) {
#if !TARGET_OS_VISION
[_locationManager requestAlwaysAuthorization];
[self enableBackgroundLocationUpdates];
#endif
} else if (wantsWhenInUse) {
[_locationManager requestWhenInUseAuthorization];
}
}
What's going on ?
wantsAlwaysis true when "NSLocationAlwaysUsageDescription" has a value in the Info.plist file.- If
wantsAlwaysis true, thenenableBackgroundLocationUpdatesis called. - If you followed the installation instructions, you have probably set both "NSLocationAlwaysUsageDescription" and "NSLocationWhenInUseUsageDescription"
- But, if you didn't intended to use the background location feature, you didn't setup the corresponding Capability (see docs).
And so, what is the problem with this ?
As the native code checks for "NSLocationAlwaysUsageDescription" first, it will always enter the first condition block and call enableBackgroundLocationUpdateswhich is unable to process correctly as the capability isn't added to the app. So your request is kind of soft locked.
How to fix this?
Solution 1 : Explicitly set the config to force the authorizationLevel to "whenInUse".
Geolocation.setRNConfiguration({
skipPermissionRequests: false,
authorizationLevel: 'whenInUse',
})
Solution 2 : Add the background location capability (see: https://www.npmjs.com/package/@react-native-community/geolocation#requestauthorization:~:text=In%20order%20to%20enable%20geolocation%20in%20the%20background%2C%20you%20need%20to%20include%20the%20%27NSLocationAlwaysUsageDescription%27%20key%20in%20Info.plist%20and%20add%20location%20as%20a%20background%20mode%20in%20the%20%27Capabilities%27%20tab%20in%20Xcode.)
Conclusion I my opinion, it doesn't deserve any fix, as the "always" mode is useful only when background location is needed. But, adding a caveat to the setup docs could be a good idea.
**EDIT 2: iOS native requestWhenInUseAuthorization() behavior **
@crasyboy42 Your issue is more probably caused by this.
The iOS native docs for requestAlwaysAuthorization() tells us :
After the user makes a selection and determines the status, the location manager delivers the results to the delegate’s [locationManager(_:didChangeAuthorization:)](https://developer.apple.com/documentation/corelocation/cllocationmanagerdelegate/locationmanager(_:didchangeauthorization:)) method. If the initial authorization status is anything other than [CLAuthorizationStatus.notDetermined](https://developer.apple.com/documentation/corelocation/clauthorizationstatus/notdetermined), this method does nothing and doesn’t call the [locationManager(_:didChangeAuthorization:)](https://developer.apple.com/documentation/corelocation/cllocationmanagerdelegate/locationmanager(_:didchangeauthorization:)) method.
It means that, on the same run, calling Geolocation.requestAuthorization more than one time will not work, as the "authorization status is anything other than [CLAuthorizationStatus.notDetermined]".
My temporary solution is to fix the package (eg. using patch-package) by replacing line 291 in RNCGeolocation.mm with :
if(_lastUpdatedAuthorizationStatus == kCLAuthorizationStatusNotDetermined) {
[_locationManager requestWhenInUseAuthorization];
} else {
[self locationManagerDidChangeAuthorization:_locationManager];
}
I'll open a PR with this soon, I think its a better behavior as the module doesn't offer any way to check the CLAuthorizationStatus value.