react-native/Libraries/Utilities/BackHandler.android.js

102 lines
2.7 KiB
JavaScript
Raw Normal View History

/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const DeviceEventManager = require('NativeModules').DeviceEventManager;
const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
const DEVICE_BACK_EVENT = 'hardwareBackPress';
type BackPressEventName = $Enum<{
backPress: string,
}>;
const _backPressSubscriptions = new Set();
RCTDeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function() {
let invokeDefault = true;
const subscriptions = Array.from(_backPressSubscriptions.values()).reverse();
Remove usage of the ... operator in BackHandler Summary: This usage of `...` currently causes BackHandler to silently become non-functional when a `Symbol` polyfill is used. Why? - The `[...obj]` operator implicitly calls the object's iterator to fill the array - react-native's internal `Set` polyfill has a load order issue that breaks it whenever a project uses a `Symbol` polyfill - This means that when `BackHandler` tries to `[...set]` a `Set` of subscriptions the result is always an empty array instead of an array of subscriptions Additionally it's worth noting that the current code is also wastefully inefficient as in order to reverse iterate over subscriptions it: - Clones the `Set` (which fills it by implicitly running the set's iterator) - Uses `[...set]` to convert the cloned set into an array (which also implicitly runs the iterator on the clone) - Finally reverses the order of the array ---- This code fixes this problem by replacing the use of multiple Set instance iterators with a simple `.forEach` loop that unshifts directly into the final array. I've tested this by opening the repo's RNTester app on Android and tvOS ensuring that the back handler works before changes, the application crashes when I introduce an error (to verify my code changes are being applied to the app), the back handler works and after changes. Fixes #11968 Closes https://github.com/facebook/react-native/pull/15182 Differential Revision: D6114696 Pulled By: hramos fbshipit-source-id: 2eae127558124a394bb3572a6381a5985ebf9d64
2017-10-21 00:49:21 +00:00
for (let i = 0; i < subscriptions.length; ++i) {
if (subscriptions[i]()) {
invokeDefault = false;
break;
}
}
if (invokeDefault) {
BackHandler.exitApp();
}
});
/**
* 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;
* });
* ```
*/
const BackHandler = {
exitApp: function() {
DeviceEventManager.invokeDefaultBackPressHandler();
},
/**
* Adds an event handler. Supported events:
*
* - `hardwareBackPress`: Fires when the Android hardware back button is pressed or when the
* tvOS menu button is pressed.
*/
addEventListener: function(
eventName: BackPressEventName,
handler: Function,
): {remove: () => void} {
_backPressSubscriptions.add(handler);
return {
remove: () => BackHandler.removeEventListener(eventName, handler),
};
},
/**
* Removes the event handler.
*/
removeEventListener: function(
eventName: BackPressEventName,
handler: Function,
): void {
_backPressSubscriptions.delete(handler);
},
};
module.exports = BackHandler;