diff --git a/Examples/UIExplorer/NetInfoExample.js b/Examples/UIExplorer/NetInfoExample.js
new file mode 100644
index 000000000..017da9921
--- /dev/null
+++ b/Examples/UIExplorer/NetInfoExample.js
@@ -0,0 +1,135 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+ NetInfo,
+ Text,
+ View
+} = React;
+
+var ReachabilitySubscription = React.createClass({
+ getInitialState() {
+ return {
+ reachabilityHistory: [],
+ };
+ },
+ componentDidMount: function() {
+ NetInfo.reachabilityIOS.addEventListener(
+ 'change',
+ this._handleReachabilityChange
+ );
+ },
+ componentWillUnmount: function() {
+ NetInfo.reachabilityIOS.removeEventListener(
+ 'change',
+ this._handleReachabilityChange
+ );
+ },
+ _handleReachabilityChange: function(reachability) {
+ var reachabilityHistory = this.state.reachabilityHistory.slice();
+ reachabilityHistory.push(reachability);
+ this.setState({
+ reachabilityHistory,
+ });
+ },
+ render() {
+ return (
+
+ {JSON.stringify(this.state.reachabilityHistory)}
+
+ );
+ }
+});
+
+var ReachabilityCurrent = React.createClass({
+ getInitialState() {
+ return {
+ reachability: null,
+ };
+ },
+ componentDidMount: function() {
+ NetInfo.reachabilityIOS.addEventListener(
+ 'change',
+ this._handleReachabilityChange
+ );
+ NetInfo.reachabilityIOS.fetch().done(
+ (reachability) => { this.setState({reachability}); }
+ );
+ },
+ componentWillUnmount: function() {
+ NetInfo.reachabilityIOS.removeEventListener(
+ 'change',
+ this._handleReachabilityChange
+ );
+ },
+ _handleReachabilityChange: function(reachability) {
+ this.setState({
+ reachability,
+ });
+ },
+ render() {
+ return (
+
+ {this.state.reachability}
+
+ );
+ }
+});
+
+var IsConnected = React.createClass({
+ getInitialState() {
+ return {
+ isConnected: null,
+ };
+ },
+ componentDidMount: function() {
+ NetInfo.isConnected.addEventListener(
+ 'change',
+ this._handleConnectivityChange
+ );
+ NetInfo.isConnected.fetch().done(
+ (isConnected) => { this.setState({isConnected}); }
+ );
+ },
+ componentWillUnmount: function() {
+ NetInfo.isConnected.removeEventListener(
+ 'change',
+ this._handleConnectivityChange
+ );
+ },
+ _handleConnectivityChange: function(isConnected) {
+ this.setState({
+ isConnected,
+ });
+ },
+ render() {
+ return (
+
+ {this.state.isConnected ? 'Online' : 'Offline'}
+
+ );
+ }
+});
+
+exports.title = 'NetInfo';
+exports.description = 'Monitor network status';
+exports.examples = [
+ {
+ title: 'NetInfo.isConnected',
+ description: 'Asyncronously load and observe connectivity',
+ render() { return ; }
+ },
+ {
+ title: 'NetInfo.reachabilityIOS',
+ description: 'Asyncronously load and observe iOS reachability',
+ render() { return ; }
+ },
+ {
+ title: 'NetInfo.reachabilityIOS',
+ description: 'Observed updates to iOS reachability',
+ render() { return ; }
+ },
+];
diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js
index b7108681e..a4f70fbcd 100644
--- a/Examples/UIExplorer/UIExplorerList.js
+++ b/Examples/UIExplorer/UIExplorerList.js
@@ -42,6 +42,7 @@ var EXAMPLES = [
require('./MapViewExample'),
require('./WebViewExample'),
require('./AppStateIOSExample'),
+ require('./NetInfoExample'),
require('./AlertIOSExample'),
require('./AdSupportIOSExample'),
require('./AppStateExample'),
diff --git a/Libraries/AppState/AppState.js b/Libraries/AppState/AppState.js
index 43b9db1a5..ca5a7e607 100644
--- a/Libraries/AppState/AppState.js
+++ b/Libraries/AppState/AppState.js
@@ -25,22 +25,4 @@ var AppState = {
};
-// This check avoids redboxing if native RKReachability library isn't included in app
-// TODO: Move reachability API into separate JS module to prevent need for this
-if (RKReachability) {
- AppState.networkReachability = new Subscribable(
- RCTDeviceEventEmitter,
- 'reachabilityDidChange',
- (resp) => resp.network_reachability,
- RKReachability.getCurrentReachability
- );
-}
-
-AppState.NetworkReachability = keyMirror({
- wifi: true,
- cell: true,
- none: true,
- unknown: true,
-});
-
module.exports = AppState;
diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js
new file mode 100644
index 000000000..59c29cb07
--- /dev/null
+++ b/Libraries/Network/NetInfo.js
@@ -0,0 +1,143 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule NetInfo
+ * @flow
+ */
+'use strict';
+
+var NativeModules = require('NativeModules');
+var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
+var RKReachability = NativeModules.RKReachability;
+
+var DEVICE_REACHABILITY_EVENT = 'reachabilityDidChange';
+
+type ChangeEventName = $Enum<{
+ change: string;
+}>;
+
+
+/**
+ * NetInfo exposes info about online/offline status
+ *
+ * == iOS Reachability
+ *
+ * Asyncronously determine if the device is online and on a cellular network.
+ *
+ * - "none" - device is offline
+ * - "wifi" - device is online and connected via wifi, or is the iOS simulator
+ * - "cell" - device is connected via Edge, 3G, WiMax, or LTE
+ * - "unknown" - error case and the network status is unknown
+ *
+ * ```
+ * NetInfo.reachabilityIOS.fetch().done((reach) => {
+ * console.log('Initial: ' + reach);
+ * });
+ * function handleFirstReachabilityChange(reach) {
+ * console.log('First change: ' + reach);
+ * NetInfo.reachabilityIOS.removeEventListener(
+ * 'change',
+ * handleFirstReachabilityChange
+ * );
+ * }
+ * NetInfo.reachabilityIOS.addEventListener(
+ * 'change',
+ * handleFirstReachabilityChange
+ * );
+ * ```
+ */
+
+var NetInfo = {};
+
+if (RKReachability) {
+ var _reachabilitySubscriptions = {};
+
+ NetInfo.reachabilityIOS = {
+ addEventListener: function (
+ eventName: ChangeEventName,
+ handler: Function
+ ): void {
+ _reachabilitySubscriptions[handler] = RCTDeviceEventEmitter.addListener(
+ DEVICE_REACHABILITY_EVENT,
+ (appStateData) => {
+ handler(appStateData.network_reachability);
+ }
+ );
+ },
+
+ removeEventListener: function(
+ eventName: ChangeEventName,
+ handler: Function
+ ): void {
+ if (!_reachabilitySubscriptions[handler]) {
+ return;
+ }
+ _reachabilitySubscriptions[handler].remove();
+ _reachabilitySubscriptions[handler] = null;
+ },
+
+ fetch: function(): Promise {
+ return new Promise((resolve, reject) => {
+ RKReachability.getCurrentReachability(
+ (resp) => {
+ resolve(resp.network_reachability);
+ },
+ reject
+ );
+ });
+ },
+ };
+
+ /**
+ *
+ * == NetInfo.isConnected
+ *
+ * Available on all platforms. Asyncronously fetch a boolean to determine
+ * internet connectivity.
+ *
+ * ```
+ * NetInfo.isConnected.fetch().done((isConnected) => {
+ * console.log('First, is ' + (isConnected ? 'online' : 'offline'));
+ * });
+ * function handleFirstConnectivityChange(isConnected) {
+ * console.log('Then, is ' + (isConnected ? 'online' : 'offline'));
+ * NetInfo.isConnected.removeEventListener(
+ * 'change',
+ * handleFirstConnectivityChange
+ * );
+ * }
+ * NetInfo.isConnected.addEventListener(
+ * 'change',
+ * handleFirstConnectivityChange
+ * );
+ * ```
+ *
+ */
+ var _isConnectedSubscriptions = {};
+ NetInfo.isConnected = {
+ addEventListener: function (
+ eventName: ChangeEventName,
+ handler: Function
+ ): void {
+ _isConnectedSubscriptions[handler] = (reachability) => {
+ handler(reachability !== 'none');
+ };
+ NetInfo.reachabilityIOS.addEventListener(eventName, _isConnectedSubscriptions[handler]);
+ },
+
+ removeEventListener: function(
+ eventName: ChangeEventName,
+ handler: Function
+ ): void {
+ NetInfo.reachabilityIOS.removeEventListener(eventName, _isConnectedSubscriptions[handler]);
+ },
+
+ fetch: function(): Promise {
+ return NetInfo.reachabilityIOS.fetch().then(
+ (reachability) => reachability !== 'none'
+ );
+ },
+ };
+}
+
+module.exports = NetInfo;
diff --git a/Libraries/ReactIOS/renderApplication.js b/Libraries/ReactIOS/renderApplication.js
index 64e26126c..176dfa72a 100644
--- a/Libraries/ReactIOS/renderApplication.js
+++ b/Libraries/ReactIOS/renderApplication.js
@@ -15,12 +15,10 @@ function renderApplication(RootComponent, initialProps, rootTag) {
rootTag,
'Expect to have a valid rootTag, instead got ', rootTag
);
- var pushNotification = initialProps.launchOptions &&
- initialProps.launchOptions.remoteNotification &&
- new PushNotificationIOS(initialProps.launchOptions.remoteNotification);
+ var initialNotification = PushNotificationIOS.popInitialNotification();
React.render(
,
rootTag
diff --git a/Libraries/Utilities/PixelRatio.js b/Libraries/Utilities/PixelRatio.js
index 0eeb074c6..f9b1ac13a 100644
--- a/Libraries/Utilities/PixelRatio.js
+++ b/Libraries/Utilities/PixelRatio.js
@@ -10,45 +10,49 @@ var Dimensions = require('Dimensions');
/**
* PixelRatio class gives access to the device pixel density.
*
- * Some examples:
- * - PixelRatio.get() === 2
- * - iPhone 4, 4S
- * - iPhone 5, 5c, 5s
- * - iPhone 6
- *
- * - PixelRatio.get() === 3
- * - iPhone 6 plus
- *
* There are a few use cases for using PixelRatio:
*
- * == Displaying a line that's as thin as the device permits
+ * ### Displaying a line that's as thin as the device permits
*
* A width of 1 is actually pretty thick on an iPhone 4+, we can do one that's
- * thinner using a width of 1 / PixelRatio.get(). It's a technique that works
+ * thinner using a width of `1 / PixelRatio.get()`. It's a technique that works
* on all the devices independent of their pixel density.
*
- * style={{ borderWidth: 1 / PixelRatio.get() }}
+ * ```
+ * style={{ borderWidth: 1 / PixelRatio.get() }}
+ * ```
*
- * == Fetching a correctly sized image
+ * ### Fetching a correctly sized image
*
* You should get a higher resolution image if you are on a high pixel density
* device. A good rule of thumb is to multiply the size of the image you display
* by the pixel ratio.
*
- * var image = getImage({
- * width: 200 * PixelRatio.get(),
- * height: 100 * PixelRatio.get()
- * });
- *
+ * ```
+ * var image = getImage({
+ * width: 200 * PixelRatio.get(),
+ * height: 100 * PixelRatio.get()
+ * });
+ *
+ * ```
*/
class PixelRatio {
+ /**
+ * Returns the device pixel density. Some examples:
+ *
+ * - PixelRatio.get() === 2
+ * - iPhone 4, 4S
+ * - iPhone 5, 5c, 5s
+ * - iPhone 6
+ * - PixelRatio.get() === 3
+ * - iPhone 6 plus
+ */
static get() {
return Dimensions.get('window').scale;
}
+}
- static startDetecting() {
- // no-op for iOS, but this is useful for other platforms
- }
-};
+// No-op for iOS, but used on the web. Should not be documented.
+PixelRatio.startDetecting = function() {};
module.exports = PixelRatio;
diff --git a/Libraries/Utilities/PushNotificationIOS.js b/Libraries/Utilities/PushNotificationIOS.js
index 0cd8a6db6..86733bde3 100644
--- a/Libraries/Utilities/PushNotificationIOS.js
+++ b/Libraries/Utilities/PushNotificationIOS.js
@@ -5,8 +5,14 @@
*/
'use strict';
+var NativeModules = require('NativeModules');
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
+var RCTPushNotificationManager = NativeModules.RCTPushNotificationManager;
+if (RCTPushNotificationManager) {
+ var _initialNotification = RCTPushNotificationManager.initialNotification;
+}
+
var _notifHandlers = {};
var DEVICE_NOTIF_EVENT = 'remoteNotificationReceived';
@@ -30,6 +36,14 @@ class PushNotificationIOS {
_notifHandlers[handler] = null;
}
+
+ static popInitialNotification() {
+ var initialNotification = _initialNotification &&
+ new PushNotificationIOS(_initialNotification);
+ _initialNotification = null;
+ return initialNotification;
+ }
+
constructor(nativeNotif) {
this._data = {};
diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js
index 00be3f65a..5e164b5fc 100644
--- a/Libraries/react-native/react-native.js
+++ b/Libraries/react-native/react-native.js
@@ -24,6 +24,7 @@ var ReactNative = {
ListViewDataSource: require('ListViewDataSource'),
MapView: require('MapView'),
NavigatorIOS: require('NavigatorIOS'),
+ NetInfo: require('NetInfo'),
PickerIOS: require('PickerIOS'),
PixelRatio: require('PixelRatio'),
ScrollView: require('ScrollView'),
diff --git a/ReactKit/Base/RCTBridge.m b/ReactKit/Base/RCTBridge.m
index 5173bc5d2..1fd446b3f 100644
--- a/ReactKit/Base/RCTBridge.m
+++ b/ReactKit/Base/RCTBridge.m
@@ -554,7 +554,7 @@ static id _latestJSExecutor;
}];
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
- RCTLogMustFix(@"JavaScriptExecutor took too long to inject JSON object");
+ RCTLogError(@"JavaScriptExecutor took too long to inject JSON object");
}
}
diff --git a/ReactKit/Modules/RCTPushNotificationManager.h b/ReactKit/Modules/RCTPushNotificationManager.h
new file mode 100644
index 000000000..e0ba53a62
--- /dev/null
+++ b/ReactKit/Modules/RCTPushNotificationManager.h
@@ -0,0 +1,12 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import
+
+extern NSString *const RKRemoteNotificationReceived;
+extern NSString *const RKOpenURLNotification;
+
+@interface RCTPushNotificationManager : NSObject
+
+- (instancetype)initWithInitialNotification:(NSDictionary *)initialNotification NS_DESIGNATED_INITIALIZER;
+
+@end
diff --git a/ReactKit/Modules/RCTPushNotificationManager.m b/ReactKit/Modules/RCTPushNotificationManager.m
new file mode 100644
index 000000000..b895f4d28
--- /dev/null
+++ b/ReactKit/Modules/RCTPushNotificationManager.m
@@ -0,0 +1,64 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import "RCTPushNotificationManager.h"
+
+#import "RCTAssert.h"
+#import "RCTBridge.h"
+#import "RCTEventDispatcher.h"
+
+NSString *const RKRemoteNotificationReceived = @"RemoteNotificationReceived";
+NSString *const RKOpenURLNotification = @"RKOpenURLNotification";
+
+@implementation RCTPushNotificationManager
+{
+ NSDictionary *_initialNotification;
+}
+
+@synthesize bridge = _bridge;
+
+- (instancetype)init
+{
+ return [self initWithInitialNotification:nil];
+}
+
+- (instancetype)initWithInitialNotification:(NSDictionary *)initialNotification
+{
+ if ((self = [super init])) {
+ _initialNotification = [initialNotification copy];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(handleRemoteNotificationReceived:)
+ name:RKRemoteNotificationReceived
+ object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(handleOpenURLNotification:)
+ name:RKOpenURLNotification
+ object:nil];
+ }
+ return self;
+}
+
+- (void)handleRemoteNotificationReceived:(NSNotification *)notification
+{
+ [_bridge.eventDispatcher sendDeviceEventWithName:@"remoteNotificationReceived"
+ body:[notification userInfo]];
+}
+
+- (void)handleOpenURLNotification:(NSNotification *)notification
+{
+ [_bridge.eventDispatcher sendDeviceEventWithName:@"openURL"
+ body:[notification userInfo]];
+}
+
+- (NSDictionary *)constantsToExport
+{
+ return @{
+ @"initialNotification": _initialNotification ?: [NSNull null]
+ };
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+@end
diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
index 122701d56..a7bf1f533 100644
--- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
+++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
@@ -134,13 +134,17 @@ DependecyGraph.prototype.resolveDependency = function(
fromModule,
depModuleId
) {
- // Process asset requires.
- var assetMatch = depModuleId.match(/^image!(.+)/);
- if (assetMatch && assetMatch[1]) {
- if (!this._assetMap[assetMatch[1]]) {
- throw new Error('Cannot find asset: ' + assetMatch[1]);
+
+ if (this._assetMap != null) {
+ // Process asset requires.
+ var assetMatch = depModuleId.match(/^image!(.+)/);
+ if (assetMatch && assetMatch[1]) {
+ if (!this._assetMap[assetMatch[1]]) {
+ console.warn('Cannot find asset: ' + assetMatch[1]);
+ return null;
+ }
+ return this._assetMap[assetMatch[1]];
}
- return this._assetMap[assetMatch[1]];
}
var packageJson, modulePath, dep;
@@ -577,7 +581,8 @@ function buildAssetMap(roots, exts) {
} else {
var ext = path.extname(file).replace(/^\./, '');
if (exts.indexOf(ext) !== -1) {
- var assetName = path.basename(file, '.' + ext);
+ var assetName = path.basename(file, '.' + ext)
+ .replace(/@[\d\.]+x/, '');
if (map[assetName] != null) {
debug('Conflcting assets', assetName);
}