diff --git a/Examples/UIExplorer/js/UIExplorerApp.android.js b/Examples/UIExplorer/js/UIExplorerApp.android.js index 683f53b1342ab1..1e13a212ceb6bd 100644 --- a/Examples/UIExplorer/js/UIExplorerApp.android.js +++ b/Examples/UIExplorer/js/UIExplorerApp.android.js @@ -25,7 +25,7 @@ const AppRegistry = require('AppRegistry'); const AsyncStorage = require('AsyncStorage'); -const BackAndroid = require('BackAndroid'); +const BackHandler = require('BackHandler'); const Dimensions = require('Dimensions'); const DrawerLayoutAndroid = require('DrawerLayoutAndroid'); const Linking = require('Linking'); @@ -73,7 +73,7 @@ class UIExplorerApp extends React.Component { state: UIExplorerNavigationState; componentWillMount() { - BackAndroid.addEventListener('hardwareBackPress', this._handleBackButtonPress); + BackHandler.addEventListener('hardwareBackPress', this._handleBackButtonPress); } componentDidMount() { diff --git a/Examples/UIExplorer/js/UIExplorerApp.ios.js b/Examples/UIExplorer/js/UIExplorerApp.ios.js index 802413c0468894..f57e4fbf8c35bb 100644 --- a/Examples/UIExplorer/js/UIExplorerApp.ios.js +++ b/Examples/UIExplorer/js/UIExplorerApp.ios.js @@ -24,6 +24,7 @@ 'use strict'; const AsyncStorage = require('AsyncStorage'); +const BackHandler = require('BackHandler'); const Linking = require('Linking'); const React = require('react'); const ReactNative = require('react-native'); @@ -68,6 +69,10 @@ class UIExplorerApp extends React.Component { props: Props; state: UIExplorerNavigationState; + componentWillMount() { + BackHandler.addEventListener('hardwareBackPress', this._handleBack); + } + componentDidMount() { Linking.getInitialURL().then((url) => { AsyncStorage.getItem(APP_STATE_KEY, (err, storedString) => { diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 063ffb9bb82cb9..fc5c419c714a6b 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -678,6 +678,7 @@ const ScrollView = React.createClass({ } const refreshControl = this.props.refreshControl; + if (refreshControl) { if (Platform.OS === 'ios') { // On iOS the RefreshControl is a child of the ScrollView. diff --git a/Libraries/ReactNative/renderApplication.js b/Libraries/ReactNative/renderApplication.js index 26cf1bed7f143a..0175b5c6f58e6e 100644 --- a/Libraries/ReactNative/renderApplication.js +++ b/Libraries/ReactNative/renderApplication.js @@ -18,8 +18,8 @@ var ReactNative = require('ReactNative'); var invariant = require('fbjs/lib/invariant'); -// require BackAndroid so it sets the default handler that exits the app if no listeners respond -require('BackAndroid'); +// require BackHandler so it sets the default handler that exits the app if no listeners respond +require('BackHandler'); function renderApplication( RootComponent: ReactClass, diff --git a/Libraries/Utilities/BackAndroid.ios.js b/Libraries/Utilities/BackAndroid.ios.js deleted file mode 100644 index 3993c918294878..00000000000000 --- a/Libraries/Utilities/BackAndroid.ios.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 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. - * - * iOS stub for BackAndroid.android.js - * - * @providesModule BackAndroid - */ - -'use strict'; - -function emptyFunction() {} - -const BackAndroid = { - exitApp: emptyFunction, - addEventListener() { - return { - remove: emptyFunction, - }; - }, - removeEventListener: emptyFunction, -}; - -module.exports = BackAndroid; diff --git a/Libraries/Utilities/BackAndroid.js b/Libraries/Utilities/BackAndroid.js new file mode 100644 index 00000000000000..0289b34c65d82c --- /dev/null +++ b/Libraries/Utilities/BackAndroid.js @@ -0,0 +1,49 @@ +/** + * 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. + * + * BackAndroid has been moved to BackHandler. This stub calls BackHandler methods + * after generating a warning to remind users to move to the new BackHandler module. + * + * @providesModule BackAndroid + */ + +'use strict'; + +var BackHandler = require('BackHandler'); + +var warning = require('fbjs/lib/warning'); + +/** + * Deprecated. Use BackHandler instead. + */ +var BackAndroid = { + + exitApp: function() { + warning(false, 'BackAndroid is deprecated. Please use BackHandler instead.'); + BackHandler.exitApp(); + }, + + addEventListener: function ( + eventName: BackPressEventName, + handler: Function + ): {remove: () => void} { + warning(false, 'BackAndroid is deprecated. Please use BackHandler instead.'); + return BackHandler.addEventListener(eventName, handler); + }, + + removeEventListener: function( + eventName: BackPressEventName, + handler: Function + ): void { + warning(false, 'BackAndroid is deprecated. Please use BackHandler instead.'); + BackHandler.removeEventListener(eventName, handler); + }, + +}; + +module.exports = BackAndroid; diff --git a/Libraries/Utilities/BackAndroid.android.js b/Libraries/Utilities/BackHandler.android.js similarity index 75% rename from Libraries/Utilities/BackAndroid.android.js rename to Libraries/Utilities/BackHandler.android.js index c3295d927b37ef..d8ca9b5eb567c9 100644 --- a/Libraries/Utilities/BackAndroid.android.js +++ b/Libraries/Utilities/BackHandler.android.js @@ -6,7 +6,7 @@ * 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 BackAndroid + * @providesModule BackHandler */ 'use strict'; @@ -34,20 +34,29 @@ RCTDeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function() { } if (invokeDefault) { - BackAndroid.exitApp(); + BackHandler.exitApp(); } }); /** - * Detect hardware back button presses, and programmatically invoke the default back button + * Detect hardware button presses for back navigation. + * + * Android: Detect hardware back button presses, and programmatically invoke the default back button * functionality to exit the app if there are no listeners or if none of the listeners return true. + * + * tvOS: Detect presses of the menu button on the TV remote. (Still to be implemented: + * programmatically disable menu button handling + * functionality to exit the app if there are no listeners or if none of the listeners return true.) + * + * iOS: Not applicable. + * * The event subscriptions are called in reverse order (i.e. last registered subscription first), * and if one subscription returns true then subscriptions registered earlier will not be called. * * Example: * * ```javascript - * BackAndroid.addEventListener('hardwareBackPress', function() { + * BackHandler.addEventListener('hardwareBackPress', function() { * // this.onMainScreen and this.goBack are just examples, you need to use your own implementation here * // Typically you would use the navigator here to go to the last state. * @@ -59,7 +68,7 @@ RCTDeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function() { * }); * ``` */ -var BackAndroid = { +var BackHandler = { exitApp: function() { DeviceEventManager.invokeDefaultBackPressHandler(); @@ -71,7 +80,7 @@ var BackAndroid = { ): {remove: () => void} { _backPressSubscriptions.add(handler); return { - remove: () => BackAndroid.removeEventListener(eventName, handler), + remove: () => BackHandler.removeEventListener(eventName, handler), }; }, @@ -84,4 +93,4 @@ var BackAndroid = { }; -module.exports = BackAndroid; +module.exports = BackHandler; diff --git a/Libraries/Utilities/BackHandler.ios.js b/Libraries/Utilities/BackHandler.ios.js new file mode 100644 index 00000000000000..958ec69e5c0558 --- /dev/null +++ b/Libraries/Utilities/BackHandler.ios.js @@ -0,0 +1,116 @@ +/** + * 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. + * + * On Apple TV, this implements back navigation using the TV remote's menu button. + * On iOS, this just implements a stub. + * + * @providesModule BackHandler + */ + +'use strict'; + +const Platform = require('Platform'); +const TVEventHandler = require('TVEventHandler'); + +type BackPressEventName = $Enum<{ + backPress: string, +}>; + +function emptyFunction() {} + +/** + * Detect hardware button presses for back navigation. + * + * Android: Detect hardware back button presses, and programmatically invoke the default back button + * functionality to exit the app if there are no listeners or if none of the listeners return true. + * + * tvOS: Detect presses of the menu button on the TV remote. (Still to be implemented: + * programmatically disable menu button handling + * functionality to exit the app if there are no listeners or if none of the listeners return true.) + * + * iOS: Not applicable. + * + * The event subscriptions are called in reverse order (i.e. last registered subscription first), + * and if one subscription returns true then subscriptions registered earlier will not be called. + * + * Example: + * + * ```javascript + * BackHandler.addEventListener('hardwareBackPress', function() { + * // this.onMainScreen and this.goBack are just examples, you need to use your own implementation here + * // Typically you would use the navigator here to go to the last state. + * + * if (!this.onMainScreen()) { + * this.goBack(); + * return true; + * } + * return false; + * }); + * ``` + */ +let BackHandler; + +if (Platform.isTVOS) { + const _tvEventHandler = new TVEventHandler(); + var _backPressSubscriptions = new Set(); + + _tvEventHandler.enable(this, function(cmp, evt) { + if (evt && evt.eventType === 'menu') { + var backPressSubscriptions = new Set(_backPressSubscriptions); + var invokeDefault = true; + var subscriptions = [...backPressSubscriptions].reverse(); + for (var i = 0; i < subscriptions.length; ++i) { + if (subscriptions[i]()) { + invokeDefault = false; + break; + } + } + + if (invokeDefault) { + BackHandler.exitApp(); + } + } + }); + + BackHandler = { + exitApp: emptyFunction, + + addEventListener: function ( + eventName: BackPressEventName, + handler: Function + ): {remove: () => void} { + _backPressSubscriptions.add(handler); + return { + remove: () => BackHandler.removeEventListener(eventName, handler), + }; + }, + + removeEventListener: function( + eventName: BackPressEventName, + handler: Function + ): void { + _backPressSubscriptions.delete(handler); + }, + + }; + +} else { + + BackHandler = { + exitApp: emptyFunction, + addEventListener() { + return { + remove: emptyFunction, + }; + }, + removeEventListener: emptyFunction, + }; + +} + +module.exports = BackHandler; diff --git a/Libraries/react-native/react-native-implementation.js b/Libraries/react-native/react-native-implementation.js index 1ee23e0fa19dd8..8cb0002bcca576 100644 --- a/Libraries/react-native/react-native-implementation.js +++ b/Libraries/react-native/react-native-implementation.js @@ -81,7 +81,8 @@ const ReactNative = { get AppRegistry() { return require('AppRegistry'); }, get AppState() { return require('AppState'); }, get AsyncStorage() { return require('AsyncStorage'); }, - get BackAndroid() { return require('BackAndroid'); }, + get BackAndroid() { return require('BackAndroid'); }, // deprecated: use BackHandler instead + get BackHandler() { return require('BackHandler'); }, get CameraRoll() { return require('CameraRoll'); }, get Clipboard() { return require('Clipboard'); }, get DatePickerAndroid() { return require('DatePickerAndroid'); }, @@ -106,6 +107,7 @@ const ReactNative = { get StyleSheet() { return require('StyleSheet'); }, get Systrace() { return require('Systrace'); }, get TimePickerAndroid() { return require('TimePickerAndroid'); }, + get TVEventHandler() { return require('TVEventHandler'); }, get UIManager() { return require('UIManager'); }, get Vibration() { return require('Vibration'); }, get VibrationIOS() { return require('VibrationIOS'); }, diff --git a/website/server/docsList.js b/website/server/docsList.js index 5e912d684b1894..4fdd3720efa83f 100644 --- a/website/server/docsList.js +++ b/website/server/docsList.js @@ -59,7 +59,9 @@ const apis = [ '../Libraries/ReactNative/AppRegistry.js', '../Libraries/AppState/AppState.js', '../Libraries/Storage/AsyncStorage.js', - '../Libraries/Utilities/BackAndroid.android.js', + '../Libraries/Utilities/BackAndroid.js', + '../Libraries/Utilities/BackHandler.ios.js', + '../Libraries/Utilities/BackHandler.android.js', '../Libraries/CameraRoll/CameraRoll.js', '../Libraries/Components/Clipboard/Clipboard.js', '../Libraries/Components/DatePickerAndroid/DatePickerAndroid.android.js',