React Native - Device back button handling

后端 未结 9 1970
感情败类
感情败类 2020-12-02 18:33

I want to check if there are more than one screens are on stack when device back button is hit. If yes, I want to show previous screen and if no, I want to exit app.

相关标签:
9条回答
  • 2020-12-02 18:53

    In react hooks

    import { BackHandler } from 'react-native';
    
    function handleBackButtonClick() {
        navigation.goBack();
        return true;
      }
    
      useEffect(() => {
        BackHandler.addEventListener('hardwareBackPress', handleBackButtonClick);
        return () => {
          BackHandler.removeEventListener('hardwareBackPress', handleBackButtonClick);
        };
      }, []);
    
    0 讨论(0)
  • 2020-12-02 18:53
    constructor(props){
        super(props)
        this.onBackPress = this.onBackPress.bind(this);
    }
    
    componentWillMount() {
            BackHandler.addEventListener('hardwareBackPress', this.onBackPress);
    
    }
    
    componentWillUnmount(){
        BackHandler.removeEventListener('hardwareBackPress', this.onBackPress);
    }
    
    onBackPress(){
        const {dispatch, nav} = this.props;
        if (nav.index < 0) {
            return false;
        }
        dispatch(NavigationActions.back());
        return true;
    }
    
    render(){
        const {dispatch, nav} = this.props;
        return(
            <DrawerRouter
                navigation= {
                    addNavigationHelpers({
                        dispatch,
                        state: nav,
                        addListener,
                    })
                }
            />
        );
    }
    
    0 讨论(0)
  • 2020-12-02 18:58
     import { BackHandler } from 'react-native';
    
     constructor() {
            super();           
            this.handleBackButtonClick = this.handleBackButtonClick.bind(this);
     }
    
       componentWillMount() {
           BackHandler.addEventListener('hardwareBackPress', this.handleBackButtonClick);
       }
    
       componentWillUnmount() {
           BackHandler.removeEventListener('hardwareBackPress', this.handleBackButtonClick);
       }
    
       handleBackButtonClick() {
           //this.props.navigation.goBack(null);
           BackHandler.exitApp();
           return true;
       }
    
    0 讨论(0)
  • 2020-12-02 19:00

    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 /> }
    }
    
    0 讨论(0)
  • 2020-12-02 19:04

    This example will show you back navigation which is expected generally in most of the flows. You will have to add following code to every screen depending on expected behavior. There are 2 cases: 1. If there are more than 1 screen on stack, device back button will show previous screen. 2. If there is only 1 screen on stack, device back button will exit app.

    Case 1: Show previous screen

    import { BackHandler } from 'react-native';
    
    constructor(props) {
        super(props)
        this.handleBackButtonClick = this.handleBackButtonClick.bind(this);
    }
    
    componentWillMount() {
        BackHandler.addEventListener('hardwareBackPress', this.handleBackButtonClick);
    }
    
    componentWillUnmount() {
        BackHandler.removeEventListener('hardwareBackPress', this.handleBackButtonClick);
    }
    
    handleBackButtonClick() {
        this.props.navigation.goBack(null);
        return true;
    }
    

    Important: Don't forget to bind method in constructor and to remove listener in componentWillUnmount.

    Case 2: Exit App

    In this case, no need to handle anything on that screen where you want to exit app.

    Important: This should be only screen on stack.

    0 讨论(0)
  • 2020-12-02 19:04

    I used flux for navigation.

        const RouterComp = () => {
    
        let backLoginScene=false;
    
        return (
    
            <Router
            backAndroidHandler={() => {
                const back_button_prohibited = ['login','userInfo','dashboard'];
                if (back_button_prohibited.includes(Actions.currentScene) ) {
                    if (backLoginScene == false) {
                        ToastAndroid.show("Click back again to exit.", ToastAndroid.SHORT);
                        backLoginScene = !backLoginScene;
                        setTimeout(() => {
                            backLoginScene = false;
                        }, 2000);
                        return true;
                    } else {
                        backLoginScene = false;
                        BackHandler.exitApp();
                    }
                    return false;
                }}}>
                <Scene key='root' hideNavBar>
                    <Scene key='guest' hideNavBar >
                        <Scene key='login' component={Login} ></Scene>
                        <Scene key='userInfo' component={UserInfo}></Scene>
                    </Scene>
    
                    <Scene key='user' hideNavBar>
                        <Scene key='dashboard' component={Dashboard} title='Dashboard' initial />
                        <Scene key='newAd' component={NewAd} title='New Ad' />
    
                    </Scene>
    
    
    
                </Scene>
            </Router>
        )
    }
    
    export default RouterComp;
    
    0 讨论(0)
提交回复
热议问题