[ReactNative] Migrate navigator.geolocation to open source

This commit is contained in:
Christopher Chedeau 2015-02-28 20:46:42 -08:00
parent c352cb1c9a
commit 878bc9d491
8 changed files with 374 additions and 0 deletions

View File

@ -0,0 +1,72 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule GeoLocationExample
*/
/* eslint no-console: 0 */
'use strict';
var React = require('react-native');
var {
StyleSheet,
Text,
View,
} = React;
exports.framework = 'React';
exports.title = 'GeoLocation';
exports.description = 'Examples of using the GeoLocation API.';
exports.examples = [
{
title: 'navigator.geolocation',
render: function() {
return <GeoLocationExample />;
},
}
];
var GeoLocationExample = React.createClass({
getInitialState: function() {
return {
initialPosition: 'unknown',
lastPosition: 'unknown',
};
},
componentDidMount: function() {
navigator.geolocation.getCurrentPosition(
(initialPosition) => this.setState({initialPosition}),
(error) => console.error(error)
);
this.watchID = navigator.geolocation.watchPosition((lastPosition) => {
this.setState({lastPosition});
});
},
componentWillUnmount: function() {
navigator.geolocation.clearWatch(this.watchID);
},
render: function() {
return (
<View>
<Text>
<Text style={styles.title}>Initial position: </Text>
{JSON.stringify(this.state.initialPosition)}
</Text>
<Text>
<Text style={styles.title}>Current position: </Text>
{JSON.stringify(this.state.lastPosition)}
</Text>
</View>
);
}
});
var styles = StyleSheet.create({
title: {
fontWeight: 'bold',
},
});

View File

@ -34,6 +34,8 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSLocationWhenInUseUsageDescription</key>
<string>You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*!</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>

View File

@ -31,6 +31,7 @@ var EXAMPLES = [
require('./TouchableExample'),
require('./ActivityIndicatorExample'),
require('./ScrollViewExample'),
require('./GeoLocationExample'),
];
var UIExplorerList = React.createClass({

View File

@ -0,0 +1,98 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule GeoLocation
*/
'use strict';
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var RCTLocationObserver = require('NativeModules').RCTLocationObserver;
var invariant = require('invariant');
var logError = require('logError');
var warning = require('warning');
var subscriptions = [];
var updatesEnabled = false;
var ensureObserving = function() {
if (!updatesEnabled) {
RCTLocationObserver.startObserving();
updatesEnabled = true;
}
};
/**
* /!\ ATTENTION /!\
* You need to add NSLocationWhenInUseUsageDescription key
* in Info.plist to enable geolocation, otherwise it's going
* to *fail silently*!
* \!/ \!/
*
* GeoLocation follows the MDN specification:
* https://developer.mozilla.org/en-US/docs/Web/API/Geolocation
*/
class GeoLocation {
static getCurrentPosition(geo_success, geo_error, geo_options) {
invariant(
typeof geo_success === 'function',
'Must provide a valid geo_success callback.'
);
if (geo_options) {
warning('geo_options are not yet supported.');
}
ensureObserving();
RCTLocationObserver.getCurrentPosition(
geo_success,
geo_error || logError
);
}
static watchPosition(callback) {
ensureObserving();
var watchID = subscriptions.length;
subscriptions.push(
RCTDeviceEventEmitter.addListener(
'geoLocationDidChange',
callback
)
);
return watchID;
}
static clearWatch(watchID) {
var sub = subscriptions[watchID];
if (!sub) {
// Silently exit when the watchID is invalid or already cleared
// This is consistent with timers
return;
}
sub.remove();
subscriptions[watchID] = undefined;
var noWatchers = true;
for (var ii = 0; ii < subscriptions.length; ii++) {
if (subscriptions[ii]) {
noWatchers = false; // still valid subscriptions
}
}
if (noWatchers) {
GeoLocation.stopObserving();
}
}
static stopObserving() {
if (updatesEnabled) {
RCTLocationObserver.stopObserving();
updatesEnabled = false;
for (var ii = 0; ii < subscriptions.length; ii++) {
if (subscriptions[ii]) {
warning('Called stopObserving with existing subscriptions.');
subscriptions[ii].remove();
}
}
subscriptions = [];
} else {
warning('Tried to stop observing when not observing.');
}
}
}
module.exports = GeoLocation;

View File

@ -139,9 +139,15 @@ function setupXHR() {
GLOBAL.fetch = require('fetch');
}
function setupGeolocation() {
GLOBAL.navigator = GLOBAL.navigator || {};
GLOBAL.navigator.geolocation = require('GeoLocation');
}
setupRedBoxErrorHandler();
setupDocumentShim();
setupTimers();
setupAlert();
setupPromise();
setupXHR();
setupGeolocation();

View File

@ -0,0 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTBridgeModule.h"
@interface RCTLocationObserver : NSObject<RCTBridgeModule>
@end

View File

@ -0,0 +1,182 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTLocationObserver.h"
#import <CoreLocation/CLLocationManager.h>
#import <CoreLocation/CLLocationManagerDelegate.h>
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
// TODO (#5906496): Shouldn't these be configurable?
const CLLocationAccuracy RCTLocationAccuracy = 500.0; // meters
@interface RCTPendingLocationRequest : NSObject
@property (nonatomic, copy) RCTResponseSenderBlock successBlock;
@property (nonatomic, copy) RCTResponseSenderBlock errorBlock;
@end
@implementation RCTPendingLocationRequest @end
@interface RCTLocationObserver () <CLLocationManagerDelegate>
@end
@implementation RCTLocationObserver
{
CLLocationManager *_locationManager;
RCTEventDispatcher *_eventDispatcher;
NSDictionary *_lastLocationEvent;
NSMutableDictionary *_pendingRequests;
}
#pragma mark - Lifecycle
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_eventDispatcher = bridge.eventDispatcher;
_pendingRequests = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc
{
[_locationManager stopUpdatingLocation];
}
#pragma mark - Public API
- (void)startObserving
{
RCT_EXPORT();
dispatch_async(dispatch_get_main_queue(), ^{
// Create the location manager if this object does not
// already have one, and it must be created and accessed
// on the main thread
if (nil == _locationManager) {
_locationManager = [[CLLocationManager alloc] init];
}
_locationManager.delegate = self;
_locationManager.desiredAccuracy = RCTLocationAccuracy;
// Set a movement threshold for new events.
_locationManager.distanceFilter = RCTLocationAccuracy; // meters
if([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
[_locationManager requestWhenInUseAuthorization];
}
[_locationManager startUpdatingLocation];
});
}
- (void)stopObserving
{
RCT_EXPORT();
dispatch_async(dispatch_get_main_queue(), ^{
[_locationManager stopUpdatingLocation];
_lastLocationEvent = nil;
});
}
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *loc = [locations lastObject];
NSDictionary *event = @{
@"coords": @{
@"latitude": @(loc.coordinate.latitude),
@"longitude": @(loc.coordinate.longitude),
@"altitude": @(loc.altitude),
@"accuracy": @(RCTLocationAccuracy),
@"altitudeAccuracy": @(RCTLocationAccuracy),
@"heading": @(loc.course),
@"speed": @(loc.speed),
},
@"timestamp": @(CACurrentMediaTime())
};
[_eventDispatcher sendDeviceEventWithName:@"geoLocationDidChange" body:event];
NSArray *pendingRequestsCopy;
// TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize
@synchronized(self) {
pendingRequestsCopy = [_pendingRequests allValues];
[_pendingRequests removeAllObjects];
_lastLocationEvent = event;
}
for (RCTPendingLocationRequest *request in pendingRequestsCopy) {
if (request.successBlock) {
request.successBlock(@[event]);
}
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSArray *pendingRequestsCopy;
// TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize
@synchronized(self) {
pendingRequestsCopy = [_pendingRequests allValues];
[_pendingRequests removeAllObjects];
}
NSString *errorMsg = @"User denied location service or location service not available.";
for (RCTPendingLocationRequest *request in pendingRequestsCopy) {
if (request.errorBlock) {
request.errorBlock(@[errorMsg]);
}
}
}
- (void)getCurrentPosition:(RCTResponseSenderBlock)geoSuccess withErrorCallback:(RCTResponseSenderBlock)geoError
{
RCT_EXPORT();
NSDictionary *lastLocationCopy;
// TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize
@synchronized(self) {
if (![CLLocationManager locationServicesEnabled] || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
if (geoError) {
NSString *errorMsg = @"User denied location service or location service not available.";
geoError(@[errorMsg]);
return;
}
}
// If a request for the current position comes in before the OS has informed us, we wait for the first
// OS event and then call our callbacks. This obviates the need for handling of the otherwise
// common failure case of requesting the geolocation until it succeeds, assuming we would have
// instead returned an error if it wasn't yet available.
if (!_lastLocationEvent) {
NSInteger requestID = [_pendingRequests count];
RCTPendingLocationRequest *request = [[RCTPendingLocationRequest alloc] init];
request.successBlock = geoSuccess;
request.errorBlock = geoError;
_pendingRequests[@(requestID)] = request;
return;
} else {
lastLocationCopy = [_lastLocationEvent copy];
}
}
if (geoSuccess) {
geoSuccess(@[lastLocationCopy]);
}
}
@end

View File

@ -32,6 +32,7 @@
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; };
13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; };
13E067591A70F44B002CDEE1 /* UIView+ReactKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */; };
5F5F0D991A9E456B001279FA /* RCTLocationObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */; };
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; };
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; };
832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; };
@ -113,6 +114,8 @@
13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+ReactKit.m"; sourceTree = "<group>"; };
13ED13891A80C9D40050A8F9 /* RCTPointerEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointerEvents.h; sourceTree = "<group>"; };
13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSMethodRegistrar.h; sourceTree = "<group>"; };
5F5F0D971A9E456B001279FA /* RCTLocationObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTLocationObserver.h; sourceTree = "<group>"; };
5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLocationObserver.m; sourceTree = "<group>"; };
830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = "<group>"; };
830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = "<group>"; };
830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = "<group>"; };
@ -177,6 +180,8 @@
13B07FE01A69315300A75B9A /* Modules */ = {
isa = PBXGroup;
children = (
5F5F0D971A9E456B001279FA /* RCTLocationObserver.h */,
5F5F0D981A9E456B001279FA /* RCTLocationObserver.m */,
13B07FE71A69327A00A75B9A /* RCTAlertManager.h */,
13B07FE81A69327A00A75B9A /* RCTAlertManager.m */,
13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */,
@ -376,6 +381,7 @@
13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */,
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,
5F5F0D991A9E456B001279FA /* RCTLocationObserver.m in Sources */,
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */,
1302F0FD1A78550100EBEF02 /* RCTStaticImage.m in Sources */,
13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */,