BackAndroid imported with :
var {
AppRegistry,
StyleSheet,
TouchableHighlight,
Text,
Navigator,
View,
ListView,
ToolbarAndroid,
BackAndroid,
TextInput,
} = React;
BackAndroid.addEventListener('hardwareBackPress', function() {
return true;
});
-> still quit.
On the docs, it says if it return true, app should not quit.
Here is an extremely minified version of my app : https://rnplay.org/apps/Ss8E8Q
On the demo, it says "unfortunately, the app has stopped."
On my phone, it says nothing.
If it can helps:
Tried using LG G2 on CloudyG2 with 5.0.2.
Will try on Nexus 5 (5.1.1) and genymotion later.
I had to add the following to MainActivity.java,
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
@satya164 your code works!
@satya164 Thank's a lot, it works.
Maybe it should be added to the docs ?
@vincelwt It was absent from the boilerplate app previously. I believe it has been added.
Yes, the back button handler will be added to all new apps by react-native init by default in the 0.12 release: https://github.com/facebook/react-native/blob/master/local-cli/generator-android/templates/package/MainActivity.java
Closing this on as @satya164 provided the correct solution for existing apps.
Thanks for reporting!
Hi
I have included the code snippet in MainActivity. But Here is my observation:
When the app loads and the first scene is rendered, here when i press the hardware back button, the app quits because I am returning false from my implementation of BackAndroid.
But when I move to the second scene and again when I come back to the first scene and then press the hardware back button, the app does not quit.
Is this the default expected behaviour? My implementation is as follows:
BackAndroid.addEventListener('hardwareBackPress', () => {return false;});
Another Observation is:
When the app loads the third scene and when back button is pressed there, Instead of just going back one scene, It goes back to the first scene.
The implementation is as follows:
BackAndroid.addEventListener('hardwareBackPress', () => {if (this.props.nav) {this.props.nav.pop(); return true;} return false;});
@satya164 @mkonicek Any comments?
Thanks in Advance
Lalith
I have included code, and I got
mReactInstanceManager has private access in ReactActivity
if (mReactInstanceManager != null) {
^
@TeodorKolev So do I, any solution?
I have a question here. If I add these code in MainActivity:
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
Did I need to add BackAndroid Listener in JS? Maybe just leave one of them? Any suggestions?
mReactInstanceManager is private. So I can not add this to MainActivity
@TeodorKolev you need import this package in file:
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactInstanceManager;
public class MainActivity extends ReactActivity {
private ReactInstanceManager mReactInstanceManager;
[...]
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
Leaving for any posterity...
I am on v0.46.0 of react-native and had the same issue. I tracked the issue down to this file in the react-native code base
https://github.com/facebook/react-native/blob/master/Libraries/Utilities/BackHandler.android.js#L25
When running with the chrome debugger turned off the line
var subscriptions = Array.from(_backPressSubscriptions.values()).reverse()
always returns an empty array for subscriptions which in turn causes the invokeDefault variable to stay true and the .exitApp() function to be called.
After more investigation, I think the issue was discovered and discussed in the following PR https://github.com/facebook/react-native/pull/15182.
Even after copy/pasting the PR change in an older version of RN it did not work most likely caused by the issue described in the PR.
After some very slight modifications I got it working by changing to
RCTDeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function() {
var invokeDefault = true;
var subscriptions = []
_backPressSubscriptions.forEach(sub => subscriptions.push(sub))
for (var i = 0; i < subscriptions.reverse().length; ++i) {
if (subscriptions[i]()) {
invokeDefault = false;
break;
}
}
if (invokeDefault) {
BackHandler.exitApp();
}
});
Simply using a .forEach which was the original implementation on the PR before the amended Array.from syntax works throughout.
So you could fork react-native and use a modified version, submit a PR though I imagine that will take a little while to be approved and merged upstream, or you can do something similar to what I did which was to override the RCTDeviceEventEmitter.addListener(...) for the hardwareBackPress event.
// other imports
import { BackHandler, DeviceEventEmitter } from 'react-native'
class MyApp extends Component {
constructor(props) {
super(props)
this.backPressSubscriptions = new Set()
}
componentDidMount = () => {
DeviceEventEmitter.removeAllListeners('hardwareBackPress')
DeviceEventEmitter.addListener('hardwareBackPress', () => {
let invokeDefault = true
const subscriptions = []
this.backPressSubscriptions.forEach(sub => subscriptions.push(sub))
for (let i = 0; i < subscriptions.reverse().length; i += 1) {
if (subscriptions[i]()) {
invokeDefault = false
break
}
}
if (invokeDefault) {
BackHandler.exitApp()
}
})
this.backPressSubscriptions.add(this.handleHardwareBack)
}
componentWillUnmount = () => {
DeviceEventEmitter.removeAllListeners('hardwareBackPress')
this.backPressSubscriptions.clear()
}
handleHardwareBack = () => { /* do your thing */ }
render() { return <YourApp /> }
}
I cannot overstate how much you just saved my life on this @austenLacy
@austenLacy your solution worked like a charm! thanks a ton for your great comment!
@austenLacy Your solution worked for me. Thank you.
@austenLacy, you da bomb!
still a problem, React Native team please fix this ASAP, I'm using expo so won't be able to modify android folder :(
I upgraded my RN version to 0.55.4 from 0.52 , initially backhandler was working perfectly but after upgrade it stopped listening.
Hi, I found this is a bug of Set in react-native. Look at this code:
var set = new Set()
set.add('a')
console.warn(set.has('a'))
console.warn(Array.from(set.values()))
In node interpreter the output is true ['a'], but in react-native that is true [].
I solved it, just add the polyfill for ES6 array in the entry file (mine is App.js):
require('core-js/es6/array')
export default App {
// ...
}
Most helpful comment
Leaving for any posterity...
I am on v0.46.0 of react-native and had the same issue. I tracked the issue down to this file in the react-native code base
https://github.com/facebook/react-native/blob/master/Libraries/Utilities/BackHandler.android.js#L25
When running with the chrome debugger turned off the line
always returns an empty array for
subscriptionswhich in turn causes theinvokeDefaultvariable to staytrueand the.exitApp()function to be called.After more investigation, I think the issue was discovered and discussed in the following PR https://github.com/facebook/react-native/pull/15182.
Even after copy/pasting the PR change in an older version of RN it did not work most likely caused by the issue described in the PR.
After some very slight modifications I got it working by changing to
Simply using a
.forEachwhich was the original implementation on the PR before the amendedArray.fromsyntax works throughout.So you could fork react-native and use a modified version, submit a PR though I imagine that will take a little while to be approved and merged upstream, or you can do something similar to what I did which was to override the
RCTDeviceEventEmitter.addListener(...)for thehardwareBackPressevent.