Permalink
Browse files

Android AppState

Summary: Closes #5152

Reviewed By: svcscm

Differential Revision: D2850250

Pulled By: mkonicek

fb-gh-sync-id: 0b5063fa7121d4e304a70da8573c9ba1d05a757c
  • Loading branch information...
skv-headless authored and facebook-github-bot-3 committed Jan 21, 2016
1 parent 2263cdd commit c2d75d7a65a2bdf6e1e24a9ce887f9492be477de
@@ -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 (
+ <View>
+ <Text>{this.state.appState}</Text>
+ </View>
+ );
+ }
+ return (
+ <View>
+ <Text>{JSON.stringify(this.state.previousAppStates)}</Text>
+ </View>
+ );
+ }
+});
+
+exports.title = 'AppState';
+exports.description = 'app background status';
+exports.examples = [
+ {
+ title: 'AppState.currentState',
+ description: 'Can be null on app initialization',
+ render() { return <Text>{AppState.currentState}</Text>; }
+ },
+ {
+ title: 'Subscribed AppState:',
+ description: 'This changes according to the current state, so you can only ever see it rendered as "active"',
+ render(): ReactElement { return <AppStateSubscription showCurrentOnly={true} />; }
+ },
+ {
+ title: 'Previous states:',
+ render(): ReactElement { return <AppStateSubscription showCurrentOnly={false} />; }
+ },
+];
@@ -43,6 +43,7 @@ var COMPONENTS = [
var APIS = [
require('./AccessibilityAndroidExample.android'),
require('./AlertExample').AlertExample,
+ require('./AppStateExample'),
require('./BorderExample'),
require('./CameraRollExample'),
require('./ClipboardExample'),
@@ -64,6 +64,7 @@ var APIS = [
require('./AnimatedExample'),
require('./AnimatedGratuitousApp/AnExApp'),
require('./AppStateIOSExample'),
+ require('./AppStateExample'),
require('./AsyncStorageExample'),
require('./BorderExample'),
require('./BoxShadowExample'),
@@ -0,0 +1,147 @@
+/**
+ * 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 (
+ * <Text>Current state is: {this.state.currentAppState}</Text>
+ * );
+ * },
+ * ```
+ *
+ * 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(
+ (appStateData) => {
+ AppState.currentState = appStateData.app_state;
+ },
+ logError
+);
+
+module.exports = AppState;
@@ -60,6 +60,7 @@ var ReactNative = {
get AlertIOS() { return require('AlertIOS'); },
get Animated() { return require('Animated'); },
get AppRegistry() { return require('AppRegistry'); },
+ get AppState() { return require('AppState'); },
get AppStateIOS() { return require('AppStateIOS'); },
get AsyncStorage() { return require('AsyncStorage'); },
get BackAndroid() { return require('BackAndroid'); },
@@ -72,6 +72,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
AlertIOS: require('AlertIOS'),
Animated: require('Animated'),
AppRegistry: require('AppRegistry'),
+ AppState: require('AppState'),
AppStateIOS: require('AppStateIOS'),
AsyncStorage: require('AsyncStorage'),
BackAndroid: require('BackAndroid'),
@@ -0,0 +1,76 @@
+/**
+ * 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.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
+
+public class AppStateModule extends ReactContextBaseJavaModule
+ implements LifecycleEventListener {
+
+ public static final String APP_STATE_ACTIVE = "active";
+ public static final String APP_STATE_BACKGROUND = "background";
+
+ private String mAppState = "uninitialized";
+
+ public AppStateModule(ReactApplicationContext reactContext) {
+ super(reactContext);
+ }
+
+ @Override
+ public String getName() {
+ return "AppState";
+ }
+
+ @Override
+ public void initialize() {
+ getReactApplicationContext().addLifecycleEventListener(this);
+ }
+
+ @ReactMethod
+ public void getCurrentAppState(Callback success, Callback error) {
+ success.invoke(createAppStateEventMap());
+ }
+
+ @Override
+ public void onHostResume() {
+ mAppState = APP_STATE_ACTIVE;
+ sendAppStateChangeEvent();
+ }
+
+ @Override
+ public void onHostPause() {
+ mAppState = APP_STATE_BACKGROUND;
+ sendAppStateChangeEvent();
+ }
+
+ @Override
+ public void onHostDestroy() {
+ // do not set state to destroyed, do not send an event. By the current implementation, the
+ // catalyst instance is going to be immediately dropped, and all JS calls with it.
+ }
+
+ private WritableMap createAppStateEventMap() {
+ WritableMap appState = Arguments.createMap();
+ appState.putString("app_state", mAppState);
+ return appState;
+ }
+
+ private void sendAppStateChangeEvent() {
+ getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class)
+ .emit("appStateDidChange", createAppStateEventMap());
+ }
+}
@@ -26,6 +26,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;
@@ -59,6 +60,7 @@
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(
+ new AppStateModule(reactContext),
new AsyncStorageModule(reactContext),
new CameraRollManager(reactContext),
new ClipboardModule(reactContext),
@@ -68,8 +70,8 @@
new LocationModule(reactContext),
new NetworkingModule(reactContext),
new NetInfoModule(reactContext),
- new WebSocketModule(reactContext),
- new ToastModule(reactContext));
+ new ToastModule(reactContext),
+ new WebSocketModule(reactContext));
}
@Override
@@ -91,17 +93,17 @@
new ReactImageManager(),
new ReactProgressBarViewManager(),
new ReactRawTextManager(),
- new RecyclerViewBackedScrollViewManager(),
new ReactScrollViewManager(),
new ReactSwitchManager(),
+ new ReactTextInlineImageViewManager(),
new ReactTextInputManager(),
new ReactTextViewManager(),
new ReactToolbarManager(),
new ReactViewManager(),
new ReactViewPagerManager(),
- new ReactTextInlineImageViewManager(),
new ReactVirtualTextViewManager(),
- new SwipeRefreshLayoutManager(),
- new ReactWebViewManager());
+ new ReactWebViewManager(),
+ new RecyclerViewBackedScrollViewManager(),
+ new SwipeRefreshLayoutManager());
}
}
@@ -225,6 +225,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',

0 comments on commit c2d75d7

Please sign in to comment.