diff --git a/Examples/UIExplorer/AppStateExample.js b/Examples/UIExplorer/AppStateExample.js
new file mode 100644
index 00000000000000..07d70be82de7c1
--- /dev/null
+++ b/Examples/UIExplorer/AppStateExample.js
@@ -0,0 +1,80 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @providesModule AppStateExample
+ * @flow
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+ AppState,
+ Text,
+ View
+} = React;
+
+var AppStateSubscription = React.createClass({
+ getInitialState() {
+ return {
+ appState: AppState.currentState,
+ previousAppStates: [],
+ };
+ },
+ componentDidMount: function() {
+ AppState.addEventListener('change', this._handleAppStateChange);
+ },
+ componentWillUnmount: function() {
+ AppState.removeEventListener('change', this._handleAppStateChange);
+ },
+ _handleAppStateChange: function(appState) {
+ var previousAppStates = this.state.previousAppStates.slice();
+ previousAppStates.push(this.state.appState);
+ this.setState({
+ appState,
+ previousAppStates,
+ });
+ },
+ render() {
+ if (this.props.showCurrentOnly) {
+ return (
+
+ {this.state.appState}
+
+ );
+ }
+ return (
+
+ {JSON.stringify(this.state.previousAppStates)}
+
+ );
+ }
+});
+
+exports.title = 'AppState';
+exports.description = 'app background status';
+exports.examples = [
+ {
+ title: 'AppState.currentState',
+ description: 'Can be null on app initialization',
+ render() { return {AppState.currentState}; }
+ },
+ {
+ title: 'Subscribed AppState:',
+ description: 'This changes according to the current state, so you can only ever see it rendered as "active"',
+ render(): ReactElement { return ; }
+ },
+ {
+ title: 'Previous states:',
+ render(): ReactElement { return ; }
+ },
+];
diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js
index 81442e0b982e24..85fc1d116cdd5b 100644
--- a/Examples/UIExplorer/UIExplorerList.android.js
+++ b/Examples/UIExplorer/UIExplorerList.android.js
@@ -42,6 +42,7 @@ var COMPONENTS = [
var APIS = [
require('./AccessibilityAndroidExample.android'),
require('./AlertExample').AlertExample,
+ require('./AppStateExample'),
require('./BorderExample'),
require('./ClipboardExample'),
require('./GeolocationExample'),
diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js
index 1d96b2d49ce848..9d04c2b7ad64ae 100644
--- a/Examples/UIExplorer/UIExplorerList.ios.js
+++ b/Examples/UIExplorer/UIExplorerList.ios.js
@@ -64,6 +64,7 @@ var APIS = [
require('./AnimatedExample'),
require('./AnimatedGratuitousApp/AnExApp'),
require('./AppStateIOSExample'),
+ require('./AppStateExample'),
require('./AsyncStorageExample'),
require('./BorderExample'),
require('./CameraRollExample.ios'),
diff --git a/Libraries/AppState/AppState.js b/Libraries/AppState/AppState.js
new file mode 100644
index 00000000000000..1ccaf46c8d2395
--- /dev/null
+++ b/Libraries/AppState/AppState.js
@@ -0,0 +1,144 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule AppState
+ * @flow
+ */
+'use strict';
+
+var Map = require('Map');
+var NativeModules = require('NativeModules');
+var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
+var RCTAppState = NativeModules.AppState;
+
+var logError = require('logError');
+var invariant = require('invariant');
+
+var _eventHandlers = {
+ change: new Map(),
+ memoryWarning: new Map(),
+};
+
+/**
+ * `AppState` can tell you if the app is in the foreground or background,
+ * and notify you when the state changes.
+ *
+ * AppState is frequently used to determine the intent and proper behavior when
+ * handling push notifications.
+ *
+ * ### App States
+ *
+ * - `active` - The app is running in the foreground
+ * - `background` - The app is running in the background. The user is either
+ * in another app or on the home screen
+ * - `inactive` - This is a transition state that currently never happens for
+ * typical React Native apps.
+ *
+ * For more information, see
+ * [Apple's documentation](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html)
+ *
+ * ### Basic Usage
+ *
+ * To see the current state, you can check `AppState.currentState`, which
+ * will be kept up-to-date. However, `currentState` will be null at launch
+ * while `AppState` retrieves it over the bridge.
+ *
+ * ```
+ * getInitialState: function() {
+ * return {
+ * currentAppState: AppState.currentState,
+ * };
+ * },
+ * componentDidMount: function() {
+ * AppState.addEventListener('change', this._handleAppStateChange);
+ * },
+ * componentWillUnmount: function() {
+ * AppState.removeEventListener('change', this._handleAppStateChange);
+ * },
+ * _handleAppStateChange: function(currentAppState) {
+ * this.setState({ currentAppState, });
+ * },
+ * render: function() {
+ * return (
+ * Current state is: {this.state.currentAppState}
+ * );
+ * },
+ * ```
+ *
+ * This example will only ever appear to say "Current state is: active" because
+ * the app is only visible to the user when in the `active` state, and the null
+ * state will happen only momentarily.
+ */
+
+var AppState = {
+
+ /**
+ * Add a handler to AppState changes by listening to the `change` event type
+ * and providing the handler
+ */
+ addEventListener: function(
+ type: string,
+ handler: Function
+ ) {
+ invariant(
+ ['change', 'memoryWarning'].indexOf(type) !== -1,
+ 'Trying to subscribe to unknown event: "%s"', type
+ );
+ if (type === 'change') {
+ _eventHandlers[type].set(handler, RCTDeviceEventEmitter.addListener(
+ 'appStateDidChange',
+ (appStateData) => {
+ handler(appStateData.app_state);
+ }
+ ));
+ } else if (type === 'memoryWarning') {
+ _eventHandlers[type].set(handler, RCTDeviceEventEmitter.addListener(
+ 'memoryWarning',
+ handler
+ ));
+ }
+ },
+
+ /**
+ * Remove a handler by passing the `change` event type and the handler
+ */
+ removeEventListener: function(
+ type: string,
+ handler: Function
+ ) {
+ invariant(
+ ['change', 'memoryWarning'].indexOf(type) !== -1,
+ 'Trying to remove listener for unknown event: "%s"', type
+ );
+ if (!_eventHandlers[type].has(handler)) {
+ return;
+ }
+ _eventHandlers[type].get(handler).remove();
+ _eventHandlers[type].delete(handler);
+ },
+
+ // TODO: getCurrentAppState callback seems to be called at a really late stage
+ // after app launch. Trying to get currentState when mounting App component
+ // will likely to have the initial value here.
+ // Initialize to 'active' instead of null.
+ currentState: ('active' : ?string),
+
+};
+
+RCTDeviceEventEmitter.addListener(
+ 'appStateDidChange',
+ (appStateData) => {
+ AppState.currentState = appStateData.app_state;
+ }
+);
+
+RCTAppState.getCurrentAppState().then((appStateData) => {
+ AppState.currentState = appStateData.app_state;
+}).catch(logError)
+
+module.exports = AppState;
diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js
index b28f255d9c35ff..b502bd97620115 100644
--- a/Libraries/react-native/react-native.js
+++ b/Libraries/react-native/react-native.js
@@ -60,6 +60,7 @@ var ReactNative = {
get Animated() { return require('Animated'); },
get AppRegistry() { return require('AppRegistry'); },
get AppStateIOS() { return require('AppStateIOS'); },
+ get AppState() { return require('AppState'); },
get AsyncStorage() { return require('AsyncStorage'); },
get BackAndroid() { return require('BackAndroid'); },
get CameraRoll() { return require('CameraRoll'); },
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.java
new file mode 100644
index 00000000000000..466652da4ebc8f
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.modules.appstate;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.LifecycleEventListener;
+import com.facebook.react.bridge.Promise;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.WritableMap;
+
+import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
+
+public class AppStateModule extends ReactContextBaseJavaModule
+ implements LifecycleEventListener {
+
+ private String mAppState = "active";
+
+ public AppStateModule(ReactApplicationContext reactContext) {
+ super(reactContext);
+ }
+
+ @Override
+ public String getName() {
+ return "AppState";
+ }
+
+ @Override
+ public void initialize() {
+ getReactApplicationContext().addLifecycleEventListener(this);
+ }
+
+ @ReactMethod
+ public void getCurrentAppState(Promise promise) {
+ promise.resolve(createAppStateEventMap());
+ }
+
+ @Override
+ public void onHostResume() {
+ mAppState = "active";
+ sendAppStateEvent();
+ }
+
+ @Override
+ public void onHostPause() {
+ mAppState = "background";
+ sendAppStateEvent();
+ }
+
+ @Override
+ public void onHostDestroy() {
+
+ }
+
+ private WritableMap createAppStateEventMap() {
+ WritableMap appState = Arguments.createMap();
+ appState.putString("app_state", mAppState);
+ return appState;
+ }
+
+ private void sendAppStateEvent() {
+ getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class)
+ .emit("appStateDidChange", createAppStateEventMap());
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java
index e636cd1c08a532..5e415de7eb0b8d 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java
@@ -25,6 +25,7 @@
import com.facebook.react.modules.network.NetworkingModule;
import com.facebook.react.modules.storage.AsyncStorageModule;
import com.facebook.react.modules.toast.ToastModule;
+import com.facebook.react.modules.appstate.AppStateModule;
import com.facebook.react.modules.websocket.WebSocketModule;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.views.art.ARTRenderableViewManager;
@@ -64,6 +65,7 @@ public List createNativeModules(ReactApplicationContext reactConte
new LocationModule(reactContext),
new NetworkingModule(reactContext),
new NetInfoModule(reactContext),
+ new AppStateModule(reactContext),
new WebSocketModule(reactContext),
new ToastModule(reactContext));
}
diff --git a/website/server/extractDocs.js b/website/server/extractDocs.js
index 52ff2b8ae7806e..e6c0504de2b28b 100644
--- a/website/server/extractDocs.js
+++ b/website/server/extractDocs.js
@@ -224,6 +224,7 @@ var apis = [
'../Libraries/Animated/src/AnimatedImplementation.js',
'../Libraries/AppRegistry/AppRegistry.js',
'../Libraries/AppStateIOS/AppStateIOS.ios.js',
+ '../Libraries/AppState/AppState.js',
'../Libraries/Storage/AsyncStorage.js',
'../Libraries/Utilities/BackAndroid.android.js',
'../Libraries/CameraRoll/CameraRoll.js',