React Native 返回按钮退出应用程序

30

我在我的React Native应用的主屏幕中添加了Android返回按钮退出应用的功能。但是当我在其他屏幕上按下Android返回按钮时,也会触发该功能。

componentDidMount() {

    if (Platform.OS == "android") {
        BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);                           
  }
    this._setupGoogleSignin();           
    this._getUserDetails();
    const { navigate } = this.props.navigation;
    console.log("object url is", this.state.postsArray[0].url);

}

handleBackButton = () => {               
    Alert.alert(
        'Exit App',
        'Exiting the application?', [{
            text: 'Cancel',
            onPress: () => console.log('Cancel Pressed'),
            style: 'cancel'
        }, {
            text: 'OK',
            onPress: () => BackHandler.exitApp()
        }, ], {
            cancelable: false
        }
     )
     return true;
   }
componentWillUnmount() {
    BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
  }

https://dev59.com/n1QK5IYBdhLWcg3wNtTA - Mahdi Bashirpour
10个回答

44

如果您在导航到其他屏幕时仍然挂载了HomeScreen,或者在卸载HomeScreen时没有移除EventListener,则该EventListener将仍然被调用。

您应该在导航或卸载时清除EventListener。

onButtonPress = () => {
  BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
  // then navigate
  navigate('NewScreen');
}

handleBackButton = () => {
 Alert.alert(
     'Exit App',
     'Exiting the application?', [{
         text: 'Cancel',
         onPress: () = > console.log('Cancel Pressed'),
         style: 'cancel'
     }, {
         text: 'OK',
         onPress: () = > BackHandler.exitApp()
     }, ], {
         cancelable: false
     }
  )
  return true;
} 

componentDidMount() {
  BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}

componentWillUnmount() {
  BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}

@ParasWatts,请问您能否更新一下您的问题,并提供您尝试过的代码呢? - bennygenel
主屏幕是我打开应用时首先看到的屏幕。因此,当我点击硬件返回按钮时,希望退出应用程序。我已经更新了代码。 - Paras Watts
@ParasWatts 就像我在答案中所说的,我相信 HomeScreen 没有被卸载。如果你正在将屏幕推入堆栈,HomeScreen 仍将处于已挂载状态,因此你的 removeEventListener 永远不会被调用。尝试在导航到另一个屏幕之前删除侦听器。 - bennygenel
感谢您的帮助,@bennygenel。 - Paras Watts
如果你的导航逻辑在抽屉组件中,那么你可以将后退按钮监听器也移动到抽屉组件中。这样你就能在整个应用程序中控制它。 - bennygenel
显示剩余4条评论

7
如果您不希望警告信息出现在其他组件/屏幕上,而只想出现在特定的组件/屏幕中,可以按照以下步骤操作。
import { withNavigationFocus } from 'react-navigation';

class TestComponent extends Component {
  componentWillMount() {
    BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
  }

  componentWillUnmount() {
    BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
  }

  handleBackButton = () => {
    if (this.props.isFocused) {
      Alert.alert(
        'Exit App',
        'Exiting the application?',
        [
          {
            text: 'Cancel',
            onPress: () => console.log('Cancel Pressed'),
            style: 'cancel'
          },
          {
            text: 'OK',
            onPress: () => BackHandler.exitApp()
          }
        ],
        {
          cancelable: false
        }
      );
      return true;
    }
  };
} 

export default withNavigationFocus(TestComponent );

只有在TestComponent中,才会出现显示警告信息的BackHandler。


这个方法是可行的,但我发现如果我在不导航到任何屏幕的情况下加载应用程序,则应用程序将退出而不触发警报。但是,当我在应用程序内导航并返回到主视图屏幕时,警报会触发。那么,如何在不先导航到其他屏幕的情况下在主视图屏幕上触发警报呢?有什么建议吗? - Okechukwu Eze

4
我们可以在主应用容器中为 didfocus 添加订阅。我们可以添加逻辑来检查按钮是否被点击,使用静态变量。
import {  Alert,  BackHandler,  ToastAndroid } from 'react-native';
import {  StackActions } from 'react-navigation';
import { Toast } from 'native-base';
// common statless class variable.
let backHandlerClickCount = 0;

class App extends React.Component {
    constructor(props) {
      super(props);
      // add listener to didFocus
      this._didFocusSubscription = props.navigation.addListener('didFocus', payload =>
        BackHandler.addEventListener('hardwareBackPress', () => this.onBackButtonPressAndroid(payload)));
    }

    // remove listener on unmount 
    componentWillUnmount() {
      if (this._didFocusSubscription) {
        this._didFocusSubscription.remove();
      }
    }

    onBackButtonPressAndroid = () => {
      const shortToast = message => {
        // ToastAndroid.showWithGravityAndOffset(
        //     message,
        //     ToastAndroid.SHORT,
        //     ToastAndroid.BOTTOM,
        //     25,
        //     50
        // );

        // ios & android
        Toast.show({
            text: message,
            type: 'warning',
            position: 'top',
            duration: 3000,
        });
        }

        const {
          clickedPosition
        } = this.state;
        backHandlerClickCount += 1;
        if ((clickedPosition !== 1)) {
          if ((backHandlerClickCount < 2)) {
            shortToast('Press again to quit the application!');
          } else {
            BackHandler.exitApp();
          }
        }

        // timeout for fade and exit
        setTimeout(() => {
          backHandlerClickCount = 0;
        }, 2000);

        if (((clickedPosition === 1) &&
            (this.props.navigation.isFocused()))) {
          Alert.alert(
            'Exit Application',
            'Do you want to quit application?', [{
              text: 'Cancel',
              onPress: () => console.log('Cancel Pressed'),
              style: 'cancel'
            }, {
              text: 'OK',
              onPress: () => BackHandler.exitApp()
            }], {
              cancelable: false
            }
          );
        } else {
          this.props.navigation.dispatch(StackActions.pop({
            n: 1
          }));
        }
        return true;
      }

    }

2

补充其他答案

如果您正在使用'@react-navigation/native',则在backButton上设置eventListner即使在导航到子屏幕时也会起作用。为了克服这个问题,请在focus上设置eventListner,而不是使用componentDidMount(),并在屏幕上发生blur事件时将其删除。

在此处了解有关react-navigation事件的更多信息

export class sampleScreen extends Component {
constructor(props) {
    super(props);
    this.state = {
        foo: '',
        bar: '',
    };

    this._unsubscribeSiFocus = this.props.navigation.addListener('focus', e => {
        console.warn('focus signIn');
        BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
    });
    this._unsubscribeSiBlur = this.props.navigation.addListener('blur', e => {
        console.warn('blur signIn');
        BackHandler.removeEventListener(
            'hardwareBackPress',
            this.handleBackButton,
        );
    });

    onButtonPress = () => {
        BackHandler.removeEventListener(
            'hardwareBackPress',
            this.handleBackButton,
        );
    };
}

handleBackButton = () => {
    Alert.alert(
        'Exit App',
        'Exiting the application?',
        [{
                text: 'Cancel',
                onPress: () => console.log('Cancel Pressed'),
                style: 'cancel',
            },
            {
                text: 'OK',
                onPress: () => BackHandler.exitApp(),
            },
        ], {
            cancelable: false,
        },
    );
    return true;
};

componentDidMount() {
    // BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}

componentWillUnmount() {
    this._unsubscribeSiFocus();
    this._unsubscribeSiBlur();
    BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}

}


这种方法非常有帮助,因为我在“主页”堆栈中有许多堆栈。然而,我遇到了一个问题,我的“退出登录”会导航到“登录页面”。我遇到了“this._unsubscribeSiFocus is not a function”错误。注意: “登录页面”位于我的“主页”堆栈之外,实际上被包装在“抽屉导航器”中。 - LordAnari
解决方法:在注销时加载完整的堆栈(因为它始于登录页面)。对我来说更容易,因为所有堆栈的层次结构都在单个文件中定义。 - LordAnari
1
@TusharGautam,你做得很好兄弟。我认为这应该是完美的答案.... :) - Chandler Bing

2
如果您想要返回,请按两次返回键退出应用程序。
import React, {useEffect} from 'react';
import {BackHandler} from 'react-native';
import {Provider} from 'react-redux';
import Toast from 'react-native-root-toast';

let backHandlerClickCount = 0;

const App = () => {
    useEffect(() => {
        // back handle exit app
        BackHandler.addEventListener('hardwareBackPress', backButtonHandler);
        return () => {
            BackHandler.removeEventListener('hardwareBackPress', backButtonHandler);
        };
    }, []);
    const backButtonHandler = () => {
        const shortToast = message => {
            Toast.show(message, {
                duration: Toast.durations.LONG,
                position: Toast.positions.BOTTOM,
            });
        }
        let backHandlerClickCount;
        backHandlerClickCount += 1;
        if ((backHandlerClickCount < 2)) {
            shortToast('Press again to quit the application');
        } else {
            BackHandler.exitApp();
        }

        // timeout for fade and exit
        setTimeout(() => {
            backHandlerClickCount = 0;
        }, 1000);
        
        return true;
    }
    return (
        <Provider store={store}>
         ....
        </Provider>
    );
};

export default App;

1
各位请注意,问题可能不仅限于React Native。在将其与Firebase集成时要小心。最近的Firebase版本存在在React Native应用程序中集成返回按钮的问题!请将Firebase版本降级至firebase-version @5.0.3,然后重新检查是否有效!我曾遇到同样的问题并为此担心了好几天。最终我将版本降级至@5.0.3,现在返回按钮可以完美地工作了!如果仍然遇到问题,您可以降级到更低版本。

1
你可以根据当前场景(使用react-native-router-flux简单易行)动态修改BackHandler.addEventListener的回调函数。
import { Actions } from 'react-native-router-flux'

handleBackPress = () => {
 switch (Actions.currentScene) {
   case 'home':
     BackHandler.exitApp()
     break

   default: Actions.pop()
 }

 return true
}

完整的代码可以在这里找到:https://gist.github.com/omeileo/f05a068557e9f0a2d8a24ecccd2f3177

0
BackHandler.addEventListener('hardwareBackPress', function() {
    Alert.alert(
      'Thoát Khỏi Ứng Dụng',
      'Bạn có muốn thoát không?', [{
          text: 'Cancel',
          onPress: () => console.log('Cancel Pressed'),
          style: 'cancel'
      }, {
          text: 'OK',
          onPress: () => BackHandler.exitApp()
      }, ], {
          cancelable: false
      }
   )
   return true;
})

0
如果你正在寻找功能组件中的返回按键。

useEffect(() => {
        BackHandler.addEventListener('hardwareBackPress', handleBackButton);
        return () => {
            // Anything in here is fired on component unmount.
            BackHandler.removeEventListener('hardwareBackPress', handleBackButton);
        }
    }, [])

    onButtonPress = () => {
        BackHandler.removeEventListener('hardwareBackPress', handleBackButton);
        // then navigate
        //navigate('NewScreen');
    }
      
    const handleBackButton = () => {
        BackHandler.exitApp();
        return true;
    }


0

我之前遇到过这个问题,但是我用了一个相当简单的方法解决了它。我正在使用 react-navigation 4.4.1。

import React from 'react';
import { BackHandler, ToastAndroid} from 'react-native';

export default class LoginScreen extends React.Component {

    state = {
        canBeClosed: false
    }

    componentDidMount() {
        BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
    }
      
    componentWillUnmount() {
        BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
    }

    handleBackButton = () => {
        if (this.props.navigation.isFocused()) {
            if (this.state.canBeClosed)
                return this.state.canBeClosed = false;
    
            else {
                setTimeout(() => { this.state.canBeClosed = false }, 3000);    
                ToastAndroid.show("Press Again To Exit !", ToastAndroid.SHORT);
    
                return this.state.canBeClosed = true
            }   
        }
    };

 //some code

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接