react-native/Libraries/Geolocation/Geolocation.js

175 lines
4.9 KiB
JavaScript
Raw Normal View History

/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
2015-03-25 18:12:57 +00:00
* @flow
*/
'use strict';
const NativeEventEmitter = require('NativeEventEmitter');
const RCTLocationObserver = require('NativeModules').LocationObserver;
const invariant = require('fbjs/lib/invariant');
const logError = require('logError');
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
* found when Flow v0.54 was deployed. To see the error delete this comment and
* run Flow. */
const warning = require('fbjs/lib/warning');
const LocationEventEmitter = new NativeEventEmitter(RCTLocationObserver);
const Platform = require('Platform');
const PermissionsAndroid = require('PermissionsAndroid');
let subscriptions = [];
let updatesEnabled = false;
iOS: Geolocation: Allow skipping of permission prompts Summary: This change enables developers to tell the Geolocation module to skip its permissions requests so the app can manage the permissions requests on its own. React Native Android's Geolocation module already requires apps to request permissions on their own. Currently, the Geolocation module automatically makes permissions requests for you based on what you've specified in your property list file. However, the property list file doesn't always represent what permissions the app wants right now. For example, suppose an app sometimes wants location when "in use" and sometimes wants location "always" depending on what app features the user has engaged with. The Geolocation API will request the "always" location permission even if that's not what the app wants for the current scenario. This change enables developers to tell the Geolocation module to skip permission requests so that the app can explicitly ask for the appropriate permissions. By default, Geolocation will still ask for permissions so this is not a breaking change. This change adds a method to Geolocation that is not supported by the web spec for geolocation (`setRNConfiguration`). This method includes `RN` in the name to minimize the chances that the web spec will introduce an API with the same name. **Test plan (required)** Verified that Geolocation requests permissions by default and when `skipPermissionRequests` is `false`. Verified the permission requests are skipped when `skipPermissionRequests` is `true`. Also, my team is using this change in our app. Adam Comella Microsoft Corp. Closes https://github.com/facebook/react-native/pull/15096 Differential Revision: D5725563 Pulled By: javache fbshipit-source-id: fd23fedb7e57408fa0f0d0568e0f1ef2aea1cdd4
2017-08-29 11:00:41 +00:00
type GeoConfiguration = {
skipPermissionRequests: bool;
}
2015-04-24 23:10:46 +00:00
type GeoOptions = {
Use Apple's significant-change API (for iOS 11 UX) Summary: In the yet-to-be-released iOS 11, Apple has changed the way they notify the user of location services. (You can watch their session from WWDC about all the changes [here](https://developer.apple.com/videos/play/wwdc2017/713/).) The current implementation of `RCTLocationObserver` uses the standard location services from Apple. When the user has granted `Always` location permission and the application uses the background location service, the user is presented with the *_Blue Bar of Shame_* (for more information check out [this blog post](https://blog.set.gl/ios-11-location-permissions-and-avoiding-the-blue-bar-of-shame-1cee6cd93bbe)): ![image](https://user-images.githubusercontent.com/15896334/28285133-281e425c-6af9-11e7-9177-61b879ab593c.png) * Added `useSignificantChanges` boolean to the options passed. * Added `_usingSignificantChanges` boolean based on user options. If `true`, then the CLLocationManager will use functions `startMonitoringSignificantLocationChanges`/ `stopMonitoringSignificantLocationChanges` rather than the standard location services. * Changed method signature of `beginLocationUpdatesWithDesiredAccuracy` to include `useSignificantChanges` flag * Added check for new `NSLocationAlwaysAndWhenInUseUsageDescription` All unit tests passed. Tested in simulator and on device, toggling `useSignificantChanges` option when calling `watchPosition`. Results were as expected. **When `TRUE`, the _Blue Bar of Shame_ was not present.** Changes do not affect Android and location services still work as expected on Android. * Change is for iOS only * Using a different API will have different accuracy results. Adding `useSignificantChanges` as an option was by design so apps that want to have most accurate and most frequent update can still use standard location services. Closes https://github.com/facebook/react-native/pull/15062 Differential Revision: D5443331 Pulled By: javache fbshipit-source-id: 0cf5b6cd831c5a7c8c25a5ddc2e410a9aa989bf4
2017-07-24 18:15:27 +00:00
timeout?: number,
maximumAge?: number,
enableHighAccuracy?: bool,
distanceFilter: number,
Use Apple's significant-change API (for iOS 11 UX) Summary: In the yet-to-be-released iOS 11, Apple has changed the way they notify the user of location services. (You can watch their session from WWDC about all the changes [here](https://developer.apple.com/videos/play/wwdc2017/713/).) The current implementation of `RCTLocationObserver` uses the standard location services from Apple. When the user has granted `Always` location permission and the application uses the background location service, the user is presented with the *_Blue Bar of Shame_* (for more information check out [this blog post](https://blog.set.gl/ios-11-location-permissions-and-avoiding-the-blue-bar-of-shame-1cee6cd93bbe)): ![image](https://user-images.githubusercontent.com/15896334/28285133-281e425c-6af9-11e7-9177-61b879ab593c.png) * Added `useSignificantChanges` boolean to the options passed. * Added `_usingSignificantChanges` boolean based on user options. If `true`, then the CLLocationManager will use functions `startMonitoringSignificantLocationChanges`/ `stopMonitoringSignificantLocationChanges` rather than the standard location services. * Changed method signature of `beginLocationUpdatesWithDesiredAccuracy` to include `useSignificantChanges` flag * Added check for new `NSLocationAlwaysAndWhenInUseUsageDescription` All unit tests passed. Tested in simulator and on device, toggling `useSignificantChanges` option when calling `watchPosition`. Results were as expected. **When `TRUE`, the _Blue Bar of Shame_ was not present.** Changes do not affect Android and location services still work as expected on Android. * Change is for iOS only * Using a different API will have different accuracy results. Adding `useSignificantChanges` as an option was by design so apps that want to have most accurate and most frequent update can still use standard location services. Closes https://github.com/facebook/react-native/pull/15062 Differential Revision: D5443331 Pulled By: javache fbshipit-source-id: 0cf5b6cd831c5a7c8c25a5ddc2e410a9aa989bf4
2017-07-24 18:15:27 +00:00
useSignificantChanges?: bool,
2015-04-24 23:10:46 +00:00
}
/**
* The Geolocation API extends the web spec:
* https://developer.mozilla.org/en-US/docs/Web/API/Geolocation
*
* See https://facebook.github.io/react-native/docs/geolocation.html
*/
const Geolocation = {
2015-03-09 10:04:44 +00:00
iOS: Geolocation: Allow skipping of permission prompts Summary: This change enables developers to tell the Geolocation module to skip its permissions requests so the app can manage the permissions requests on its own. React Native Android's Geolocation module already requires apps to request permissions on their own. Currently, the Geolocation module automatically makes permissions requests for you based on what you've specified in your property list file. However, the property list file doesn't always represent what permissions the app wants right now. For example, suppose an app sometimes wants location when "in use" and sometimes wants location "always" depending on what app features the user has engaged with. The Geolocation API will request the "always" location permission even if that's not what the app wants for the current scenario. This change enables developers to tell the Geolocation module to skip permission requests so that the app can explicitly ask for the appropriate permissions. By default, Geolocation will still ask for permissions so this is not a breaking change. This change adds a method to Geolocation that is not supported by the web spec for geolocation (`setRNConfiguration`). This method includes `RN` in the name to minimize the chances that the web spec will introduce an API with the same name. **Test plan (required)** Verified that Geolocation requests permissions by default and when `skipPermissionRequests` is `false`. Verified the permission requests are skipped when `skipPermissionRequests` is `true`. Also, my team is using this change in our app. Adam Comella Microsoft Corp. Closes https://github.com/facebook/react-native/pull/15096 Differential Revision: D5725563 Pulled By: javache fbshipit-source-id: fd23fedb7e57408fa0f0d0568e0f1ef2aea1cdd4
2017-08-29 11:00:41 +00:00
/*
* Sets configuration options that will be used in all location requests.
*
* See https://facebook.github.io/react-native/docs/geolocation.html#setrnconfiguration
iOS: Geolocation: Allow skipping of permission prompts Summary: This change enables developers to tell the Geolocation module to skip its permissions requests so the app can manage the permissions requests on its own. React Native Android's Geolocation module already requires apps to request permissions on their own. Currently, the Geolocation module automatically makes permissions requests for you based on what you've specified in your property list file. However, the property list file doesn't always represent what permissions the app wants right now. For example, suppose an app sometimes wants location when "in use" and sometimes wants location "always" depending on what app features the user has engaged with. The Geolocation API will request the "always" location permission even if that's not what the app wants for the current scenario. This change enables developers to tell the Geolocation module to skip permission requests so that the app can explicitly ask for the appropriate permissions. By default, Geolocation will still ask for permissions so this is not a breaking change. This change adds a method to Geolocation that is not supported by the web spec for geolocation (`setRNConfiguration`). This method includes `RN` in the name to minimize the chances that the web spec will introduce an API with the same name. **Test plan (required)** Verified that Geolocation requests permissions by default and when `skipPermissionRequests` is `false`. Verified the permission requests are skipped when `skipPermissionRequests` is `true`. Also, my team is using this change in our app. Adam Comella Microsoft Corp. Closes https://github.com/facebook/react-native/pull/15096 Differential Revision: D5725563 Pulled By: javache fbshipit-source-id: fd23fedb7e57408fa0f0d0568e0f1ef2aea1cdd4
2017-08-29 11:00:41 +00:00
*
*/
setRNConfiguration: function(
config: GeoConfiguration
) {
if (RCTLocationObserver.setConfiguration) {
RCTLocationObserver.setConfiguration(config);
}
},
/*
* Request suitable Location permission based on the key configured on pList.
*
* See https://facebook.github.io/react-native/docs/geolocation.html#requestauthorization
*/
requestAuthorization: function() {
RCTLocationObserver.requestAuthorization();
},
2015-04-24 23:10:46 +00:00
/*
* Invokes the success callback once with the latest location info.
*
* See https://facebook.github.io/react-native/docs/geolocation.html#getcurrentposition
2015-04-24 23:10:46 +00:00
*/
getCurrentPosition: async function(
2015-03-25 18:12:57 +00:00
geo_success: Function,
geo_error?: Function,
2015-04-24 23:10:46 +00:00
geo_options?: GeoOptions
2015-03-26 17:06:50 +00:00
) {
invariant(
typeof geo_success === 'function',
'Must provide a valid geo_success callback.'
);
let hasPermission = true;
// Supports Android's new permission model. For Android older devices,
// it's always on.
if (Platform.OS === 'android' && Platform.Version >= 23) {
hasPermission = await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
);
if (!hasPermission) {
const status = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
);
hasPermission = status === PermissionsAndroid.RESULTS.GRANTED;
}
}
if (hasPermission) {
RCTLocationObserver.getCurrentPosition(
geo_options || {},
geo_success,
geo_error || logError,
);
}
2015-03-09 10:04:44 +00:00
},
2015-04-24 23:10:46 +00:00
/*
* Invokes the success callback whenever the location changes.
*
* See https://facebook.github.io/react-native/docs/geolocation.html#watchposition
2015-04-24 23:10:46 +00:00
*/
watchPosition: function(success: Function, error?: Function, options?: GeoOptions): number {
2015-03-09 10:04:44 +00:00
if (!updatesEnabled) {
RCTLocationObserver.startObserving(options || {});
updatesEnabled = true;
}
const watchID = subscriptions.length;
2015-03-09 10:04:44 +00:00
subscriptions.push([
LocationEventEmitter.addListener(
2015-03-09 10:04:44 +00:00
'geolocationDidChange',
success
),
error ? LocationEventEmitter.addListener(
2015-03-09 10:04:44 +00:00
'geolocationError',
error
) : null,
]);
return watchID;
2015-03-09 10:04:44 +00:00
},
2015-03-25 18:12:57 +00:00
clearWatch: function(watchID: number) {
const sub = subscriptions[watchID];
if (!sub) {
// Silently exit when the watchID is invalid or already cleared
// This is consistent with timers
return;
}
2015-03-25 18:12:57 +00:00
2015-03-09 10:04:44 +00:00
sub[0].remove();
2015-03-25 18:12:57 +00:00
// array element refinements not yet enabled in Flow
const sub1 = sub[1]; sub1 && sub1.remove();
subscriptions[watchID] = undefined;
let noWatchers = true;
for (let ii = 0; ii < subscriptions.length; ii++) {
if (subscriptions[ii]) {
noWatchers = false; // still valid subscriptions
}
}
if (noWatchers) {
2015-03-09 10:04:44 +00:00
Geolocation.stopObserving();
}
2015-03-09 10:04:44 +00:00
},
stopObserving: function() {
if (updatesEnabled) {
RCTLocationObserver.stopObserving();
updatesEnabled = false;
for (let ii = 0; ii < subscriptions.length; ii++) {
const sub = subscriptions[ii];
2015-03-25 18:12:57 +00:00
if (sub) {
warning(false, 'Called stopObserving with existing subscriptions.');
2015-03-25 18:12:57 +00:00
sub[0].remove();
// array element refinements not yet enabled in Flow
const sub1 = sub[1]; sub1 && sub1.remove();
}
}
subscriptions = [];
}
}
};
2015-03-09 10:04:44 +00:00
module.exports = Geolocation;