Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] BackAndroid 'hardwareBackPress' event not working #3223

Closed
vincelwt opened this issue Oct 4, 2015 · 20 comments
Closed

[Android] BackAndroid 'hardwareBackPress' event not working #3223

vincelwt opened this issue Oct 4, 2015 · 20 comments
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@vincelwt
Copy link

vincelwt commented Oct 4, 2015

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.

@satya164
Copy link
Contributor

satya164 commented Oct 4, 2015

I had to add the following to MainActivity.java,

@Override
public void onBackPressed() {
    if (mReactInstanceManager != null) {
        mReactInstanceManager.onBackPressed();
    } else {
        super.onBackPressed();
    }
}

@VansonLeung
Copy link

@satya164 your code works!

@vincelwt
Copy link
Author

vincelwt commented Oct 4, 2015

@satya164 Thank's a lot, it works.
Maybe it should be added to the docs ?

@satya164
Copy link
Contributor

satya164 commented Oct 4, 2015

@vincelwt It was absent from the boilerplate app previously. I believe it has been added.

@mkonicek
Copy link
Contributor

mkonicek commented Oct 5, 2015

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!

@mkonicek mkonicek closed this as completed Oct 5, 2015
@lalith26
Copy link

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

@TeodorKolev
Copy link

I have included code, and I got

mReactInstanceManager has private access in ReactActivity
        if (mReactInstanceManager != null) {
            ^

@hslls
Copy link

hslls commented Apr 7, 2016

@TeodorKolev So do I, any solution?

@KenChoi1992
Copy link

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?

@JagdishUpadhyay
Copy link

mReactInstanceManager is private. So I can not add this to MainActivity

@felipesousa
Copy link

@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();
      }
    }

@austenLacy
Copy link

austenLacy commented Jan 3, 2018

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 #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 /> }
}

@ZyphiraZircon
Copy link

I cannot overstate how much you just saved my life on this @austenLacy

@manidlou
Copy link

manidlou commented Mar 5, 2018

@austenLacy your solution worked like a charm! thanks a ton for your great comment!

@vyshakh
Copy link

vyshakh commented May 13, 2018

@austenLacy Your solution worked for me. Thank you.

@hmolotsi
Copy link

@austenLacy, you da bomb!

@rayj10
Copy link

rayj10 commented Jun 26, 2018

still a problem, React Native team please fix this ASAP, I'm using expo so won't be able to modify android folder :(

@aman1arun
Copy link

I upgraded my RN version to 0.55.4 from 0.52 , initially backhandler was working perfectly but after upgrade it stopped listening.

@tulayang
Copy link

tulayang commented Jul 1, 2018

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 [].

@tulayang
Copy link

tulayang commented Jul 1, 2018

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 {
  // ...
}

@facebook facebook locked as resolved and limited conversation to collaborators Jul 21, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 21, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests