【问题标题】:Back Button React native exit app后退按钮反应本机退出应用程序
【发布时间】:2021-07-30 19:23:15
【问题描述】:

我已经在我的主屏幕上的 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);
  }

【问题讨论】:

标签: react-native react-native-android


【解决方案1】:

如果在您导航到其他屏幕或卸载 HomeScreen 时您的 HomeScreen 仍然挂载,如果您不删除 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 你能用你试过的代码更新你的问题吗?
  • 主屏幕是我打开应用程序时打开的主屏幕。所以当我点击硬件后退按钮时想要退出应用程序。我已经更新了代码
  • @ParasWatts 就像我在回答中所说的那样,我相信 HomeScreen 不会卸载。如果您将屏幕推送到堆栈,HomeScreen 仍将被挂载,因此您的 removeEventListener 永远不会调用。在导航到另一个屏幕之前尝试删除监听器。
  • 有一个问题,我有一个抽屉,我从中导航到其他屏幕,抽屉在其他文件中呈现,导航代码也在该文件中。主屏幕正在呈现该抽屉组件,但 onpress 导航位于制作抽屉的侧边栏文件中。我该如何处理这种情况?
  • 感谢您的帮助@bennygenel
【解决方案2】:

如果您不希望警报消息出现在其他组件/屏幕中,而只显示在一个特定组件/屏幕中,您可以按照此操作。

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 );

将显示警报消息的 BackHandler 仅在 TestComponent 中有效

【讨论】:

  • 这行得通,但我意识到,当我加载应用程序而不导航到任何屏幕时,应用程序将退出而不触发警报,但是当我在应用程序中导航并返回主视图时-屏幕上会出现警报触发器。那么如何在不先导航到其他屏幕的情况下触发主视图屏幕上的警报。任何建议都可以
【解决方案3】:

我们可以在我们的主应用容器中为 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;
      }

    }

【讨论】:

    【解决方案4】:

    只是添加到其他答案

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

    Learn more about react-navigation events here

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

    }

    【讨论】:

    • 这种方法非常有用,因为我在Home 堆栈中有许多堆栈。但是,我的Sign Out 遇到问题,它将导航到Login Page。我收到this._unsubscribeSiFocus is not a function 错误。注意:Login Page 在我的 Home 堆栈之外,它基本上被包裹在 Drawer Navigator
    • 解决方法:在注销时加载完整的堆栈(因为它从登录页面开始)。这对我来说更容易,因为所有堆栈的层次结构都在一个文件中定义。
    • @TusharGautam 兄弟,你成功了。我认为这应该是完美的答案.... :)
    【解决方案5】:

    Guyz 请理解,这可能不仅仅是 react native 的问题。将其与 firebase 集成时要小心。最近的 firebase 版本存在在 react-native 应用程序中集成后退按钮的问题!!请将firebase版本降级到firebase-version @5.0.3,然后重新检查它是否有效!我有同样的问题,担心了好几天。我终于降级到@5.0.3 版本,现在后退按钮可以正常工作了!如果仍然遇到问题,您可以降级到较低版本。

    【讨论】:

      【解决方案6】:

      您始终可以根据当前场景动态修改BackHandler.addEventListener 的回调函数(使用react-native-router-flux 可以轻松实现)。

      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

      【讨论】:

        【解决方案7】:
        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;
        })
        

        【讨论】:

          【解决方案8】:

          我也遇到过这个问题,但我设法以这种非常简单的方式解决了它。我正在使用反应导航 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
          
          }
          

          【讨论】:

            【解决方案9】:

            如果你想双击退出应用程序

            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;
            

            【讨论】: