Location (iOS): Call back on requests only if they pass age and accuracy filters

Summary:
iOS pretty much always immediately updates location with the last cached location. This leads to the getCurrentPosition() API often returning this stale location. This change adds filtering to keep waiting until CLLocationManager provides a location fix that passes the requirements for each pending request.

This is potentially breaking in that clients that rely upon getCurrentPosition being extremely fast may find that location fixes take longer than before. However in such cases they should relax their requirements for maximumAge and/or accuracy.

Reviewed By: mmmulani

Differential Revision: D13889626

fbshipit-source-id: f566314ed5968151dad0839b99e0d3c9a562af13
This commit is contained in:
Albert Sun
2019-02-25 14:17:11 -08:00
committed by Facebook Github Bot
parent 6f9e47839d
commit 24b9889fb2

View File

@@ -151,12 +151,14 @@ RCT_EXPORT_MODULE()
#pragma mark - Private API
- (void)beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy distanceFilter:(CLLocationDistance)distanceFilter useSignificantChanges:(BOOL)useSignificantChanges
- (void)_beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy
distanceFilter:(CLLocationDistance)distanceFilter
useSignificantChanges:(BOOL)useSignificantChanges
{
if (!_locationConfiguration.skipPermissionRequests) {
[self requestAuthorization];
}
if (!_locationManager) {
_locationManager = [CLLocationManager new];
_locationManager.delegate = self;
@@ -172,6 +174,32 @@ RCT_EXPORT_MODULE()
[_locationManager startUpdatingLocation];
}
- (void)_stopUpdatingIfIdle {
if (_pendingRequests.count == 0 && !_observingLocation) {
_usingSignificantChanges ?
[_locationManager stopMonitoringSignificantLocationChanges] :
[_locationManager stopUpdatingLocation];
}
}
#pragma mark - Static Helpers
static BOOL locationEventValid(NSDictionary<NSString *, id> *event, RCTLocationOptions options) {
return [NSDate date].timeIntervalSince1970 - [RCTConvert NSTimeInterval:event[@"timestamp"]] < options.maximumAge &&
[event[@"coords"][@"accuracy"] doubleValue] <= options.accuracy;
}
static void checkLocationConfig()
{
#if RCT_DEV
if (!([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] ||
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"] ||
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysAndWhenInUseUsageDescription"])) {
RCTLogError(@"Either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription or NSLocationAlwaysAndWhenInUseUsageDescription key must be present in Info.plist to use geolocation.");
}
#endif
}
#pragma mark - Timeout handler
- (void)timeout:(NSTimer *)timer
@@ -181,12 +209,8 @@ RCT_EXPORT_MODULE()
request.errorBlock(@[RCTPositionError(RCTPositionErrorTimeout, message)]);
[_pendingRequests removeObject:request];
// Stop updating if no pending requests
if (_pendingRequests.count == 0 && !_observingLocation) {
_usingSignificantChanges ?
[_locationManager stopMonitoringSignificantLocationChanges] :
[_locationManager stopUpdatingLocation];
}
// Stop updating if not observing and no pending requests
[self _stopUpdatingIfIdle];
}
#pragma mark - Public API
@@ -229,9 +253,9 @@ RCT_EXPORT_METHOD(startObserving:(RCTLocationOptions)options)
_observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
}
[self beginLocationUpdatesWithDesiredAccuracy:_observerOptions.accuracy
distanceFilter:_observerOptions.distanceFilter
useSignificantChanges:_observerOptions.useSignificantChanges];
[self _beginLocationUpdatesWithDesiredAccuracy:_observerOptions.accuracy
distanceFilter:_observerOptions.distanceFilter
useSignificantChanges:_observerOptions.useSignificantChanges];
_observingLocation = YES;
}
@@ -241,11 +265,8 @@ RCT_EXPORT_METHOD(stopObserving)
_observingLocation = NO;
// Stop updating if no pending requests
if (_pendingRequests.count == 0) {
_usingSignificantChanges ?
[_locationManager stopMonitoringSignificantLocationChanges] :
[_locationManager stopUpdatingLocation];
}
[self _stopUpdatingIfIdle];
}
RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
@@ -278,9 +299,7 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
}
// Check if previous recorded location exists and is good enough
if (_lastLocationEvent &&
[NSDate date].timeIntervalSince1970 - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge &&
[_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] <= options.accuracy) {
if (_lastLocationEvent && locationEventValid(_lastLocationEvent, options)) {
// Call success block with most recent known location
successBlock(@[_lastLocationEvent]);
@@ -307,9 +326,9 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
if (_locationManager) {
accuracy = MIN(_locationManager.desiredAccuracy, accuracy);
}
[self beginLocationUpdatesWithDesiredAccuracy:accuracy
distanceFilter:options.distanceFilter
useSignificantChanges:options.useSignificantChanges];
[self _beginLocationUpdatesWithDesiredAccuracy:accuracy
distanceFilter:options.distanceFilter
useSignificantChanges:options.useSignificantChanges];
}
#pragma mark - CLLocationManagerDelegate
@@ -337,20 +356,17 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
[self sendEventWithName:@"geolocationDidChange" body:_lastLocationEvent];
}
// Fire all queued callbacks
for (RCTLocationRequest *request in _pendingRequests) {
request.successBlock(@[_lastLocationEvent]);
[request.timeoutTimer invalidate];
}
[_pendingRequests removeAllObjects];
// Stop updating if not observing
if (!_observingLocation) {
_usingSignificantChanges ?
[_locationManager stopMonitoringSignificantLocationChanges] :
[_locationManager stopUpdatingLocation];
// Fire off queued callbacks that pass maximumAge and accuracy filters
for (RCTLocationRequest *request in [_pendingRequests copy]) {
if (locationEventValid(_lastLocationEvent, request.options)) {
request.successBlock(@[_lastLocationEvent]);
[request.timeoutTimer invalidate];
[_pendingRequests removeObject:request];
}
}
// Stop updating if not observing and no pending requests
[self _stopUpdatingIfIdle];
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
@@ -384,15 +400,4 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
}
static void checkLocationConfig()
{
#if RCT_DEV
if (!([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] ||
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"] ||
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysAndWhenInUseUsageDescription"])) {
RCTLogError(@"Either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription or NSLocationAlwaysAndWhenInUseUsageDescription key must be present in Info.plist to use geolocation.");
}
#endif
}
@end