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/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'),