web-dev-qa-db-fra.com

React Native - Gestion du bouton de retour du périphérique

Je veux vérifier s'il y a plus d'un écran sur la pile lorsque le bouton de retour du périphérique est activé. Si oui, je veux afficher l'écran précédent et si non, je veux quitter l'application.

J'ai vérifié le nombre d'exemples mais ceux-ci utilisent BackAndroid et Navigator. Mais les deux sont obsolètes. BackHandler est le remplacement de BackAndroid. Et je peux montrer l'écran précédent en utilisant props.navigation.goBack (null).

Mais je ne parviens pas à trouver le code permettant de trouver le nombre d'écrans dans la pile. Je ne veux pas utiliser un navigateur obsolète!

8
Virat18

Cet exemple vous montrera la navigation de retour qui est généralement attendue dans la plupart des flux. Vous devrez ajouter le code suivant à chaque écran en fonction du comportement attendu. Il y a 2 cas: 1. S'il y a plus d'un écran sur la pile, le bouton Précédent de l'appareil affichera l'écran précédent. 2. S'il n'y a qu'un seul écran sur la pile, le bouton Retour de l'appareil quittera l'application.

Cas 1: Afficher l'écran précédent

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: N'oubliez pas de lier la méthode dans le constructeur et de supprimer le programme d'écoute dans composantWillUnmount.

Cas 2: Exit App

Dans ce cas, nul besoin de gérer quoi que ce soit sur cet écran où vous souhaitez quitter l'application.

Important: Cela ne devrait être qu'un écran sur la pile.

25
Virat18

Dans le cas où plusieurs écrans sont empilés dans la pile, le comportement du bouton Précédent par défaut dans react-native consiste à revenir à l'écran précédent de la pile. La manipulation du bouton de retour d'appareil lorsque vous n'avez qu'un seul écran pour quitter l'application nécessite un réglage personnalisé. Pourtant, ceci peut être réalisé sans avoir à ajouter de code de gestion de retour à chaque écran en modifiant la méthode getStateForAction du routeur de StackNavigator particulier.

Supposons que vous ayez le StackNavigator suivant utilisé dans l’application

const ScreenStack = StackNavigator(
  {
    'Screen1': {
      screen: Screen1
    },
    'Screen2': {
      screen: Screen2
    },
  },
  {
    initialRouteName: 'Screen1'
  }
);

La méthode getStateForAction du routeur du navigateur de pile peut être modifiée comme suit pour obtenir le comportement de retour attendu.

const defaultStackGetStateForAction =
  ScreenStack.router.getStateForAction;

ScreenStack.router.getStateForAction = (action, state) => {
  if(state.index === 0 && action.type === NavigationActions.BACK){
    BackHandler.exitApp();
    return null;
  }

  return defaultStackGetStateForAction(action, state);
};

le state.index devient 0 uniquement lorsqu'il y a un écran dans la pile.

3
Ruchi

Guyz, s'il vous plaît, comprenez que ce n'est peut-être pas seulement le problème avec Native. Soyez prudent lorsque vous l'intégrez à Firebase . La version récente de Firebase pose le problème de l'intégration du bouton Précédent dans les applications réactives! Veuillez déclasser la version de Firebase à Firebase-version @ 5.0.3, puis vérifier à nouveau si cela fonctionne ou non! J'ai eu le même problème et j'étais inquiet pendant des jours. J'ai finalement rétrogradé à la version @ 5.0.3 et maintenant le bouton de retour fonctionne parfaitement!!. Vous pouvez rétrograder aux versions inférieures si vous rencontrez toujours le problème.

2
Rishav Kumar

Je suis sur la v0.46.0 de react-native et avait le même problème. J'ai suivi le problème jusqu'à ce fichier dans la base de code reag-native

https://github.com/facebook/react-native/blob/master/Libraries/Utilities/BackHandler.Android.js#L25

Lors de l'exécution avec le débogueur de chrome désactivé la ligne

var subscriptions = Array.from(_backPressSubscriptions.values()).reverse()

renvoie toujours un tableau vide pour les abonnements, ce qui fait que la variable invokeDefault reste vraie et que la fonction .exitApp () est appelée.

Après plus d’investigation, je pense que la question a été découverte et discutée dans le PR # 15182 .

Même après avoir copié/collé le changement de PR dans une version antérieure de RN, cela n’a pas fonctionné, ce qui est probablement dû au problème décrit dans le PR.

Après de très légères modifications, je l’ai obtenu en passant à

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

En utilisant simplement un fichier .forEach, qui était la mise en œuvre originale sur le PR avant que la syntaxe modifiée de Array.from ne fonctionne.

Vous pouvez donc créer une réaction en natif et utiliser une version modifiée, soumettre un PR mais je suppose que cela prendra un certain temps pour être approuvé et fusionné en amont, ou vous pouvez faire quelque chose de similaire à ce que j'ai fait, qui consistait à remplacer le RCTDeviceEventEmitter.addListener (...) pour l'événement hardwareBackPress.

// 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 /> }
}
1
Gani Siva kumar
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
yamaha