web-dev-qa-db-ja.com

React Native-デバイスの戻るボタンの処理

デバイスの戻るボタンが押されたときに複数の画面がスタック上にあるかどうかを確認したい。はいの場合は前の画面を表示し、いいえの場合はアプリを終了します。

多くの例を確認しましたが、それらはBackAndroidとNavigatorを使用しています。ただし、どちらも非推奨です。 BackHandlerはBackAndroidの代替品です。また、props.navigation.goBack(null)を使用して前の画面を表示できます。

しかし、スタック内の画面カウントを見つけるためのコードを見つけることができません。非推奨のナビゲーターを使用したくない!

21
Virat18

この例では、ほとんどのフローで一般的に予想される戻るナビゲーションを示します。予想される動作に応じて、すべての画面に次のコードを追加する必要があります。 2つのケースがあります。1.スタックに複数の画面がある場合、デバイスの戻るボタンは前の画面を表示します。 2.スタックに画面が1つしかない場合、デバイスの戻るボタンを押すとアプリが終了します。

ケース1:前の画面を表示する

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

重要:コンストラクタでメソッドをバインドし、componentWillUnmountでリスナーを削除することを忘れないでください。

ケース2:アプリを終了する

この場合、アプリを終了する画面上で何も処理する必要はありません。

重要:これはスタック上の画面のみである必要があります。

43
Virat18

スタックに複数の画面がスタックされている場合、react-nativeのデフォルトの戻るボタンの動作は、スタック内の前の画面に戻ることです。アプリを終了する画面が1つしかない場合にデバイスの戻るボタンを押すと、カスタム設定が必要になります。ただし、特定のStackNavigatorのルーターのgetStateForActionメソッドを変更することで、画面ごとに処理コードを追加し直すことなく、これを実現できます。

アプリケーションで次のStackNavigatorが使用されているとします

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

スタックナビゲーターのルーターのgetStateForActionメソッドを次のように変更して、予期されるバック動作を実現できます。

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

state.indexは、スタックに1つの画面がある場合にのみ0になります。

3
truchiranga

これを試してください反応ナビゲーション

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


    handleBackButton = () => {

        const pushAction = StackActions.Push({
            routeName: 'DefaultSelections',
        });

        this.props.navigation.dispatch(pushAction);
    }

現在の画面は "DefaultSelections"で、戻るボタンを押すと、同じボタンに移動し、戻るボタンを無効にすると、戻るボタンが無効になります。

return true

backButtonの場合(公式ドキュメントで提案されているとおり)disablesすべての戻るボタンscreens;不要

2
Shubham Kakkar

Guyzは、react nativeの問題だけではないことを理解してください。 firebaseと統合する際は注意してください。最近のFirebaseバージョンには、反応するネイティブアプリに戻るボタンを統合する問題があります!! firebaseバージョンをfirebase-version @ 5.0.3にダウングレードし、それが機能するかどうかを再確認してください!私は同じ問題を抱えていて、何日も心配していました。私はついに@ 5.0.3バージョンにダウングレードしましたが、今では戻るボタンは完璧に機能します!問題が引き続き発生する場合は、下位バージョンにダウングレードすることができます。

2
Rishav Kumar

私はreact-nativeのv0.46.0を使用していますが、同じ問題がありました。反応ネイティブコードベースでこのファイルまで問題を追跡しました

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

chromeデバッガーを使用して実行すると、ラインがオフになりました

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

サブスクリプションに対して常に空の配列を返します。これにより、invokeDefault変数がtrueのままになり、.exitApp()関数が呼び出されます。

さらに調査した結果、この問題は次のように発見され、議論されたと思います PR#15182

古いバージョンのRNでPRの変更をコピー/貼り付けた後でも、PRに記載されている問題が原因で動作することはほとんどありませんでした。

いくつかの非常にわずかな変更を加えた後、

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

修正されたArray.from構文が機能する前に、PRの元の実装であった.forEachを使用するだけです。

したがって、react-nativeをフォークして修正バージョンを使用し、PRを送信することができますが、承認されて上流にマージされるまで少し時間がかかると思います(...)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