React Native - обратная обработка кнопки устройства

Я хочу проверить, есть ли более одного экрана в стеке, когда нажата кнопка возврата устройства. Если да, я хочу показать предыдущий экран, и если нет, я хочу выйти из приложения.

Я проверил количество примеров, но те используют BackAndroid и Navigator. Но оба они устарели. BackHandler заменяет BackAndroid. И я могу показать предыдущий экран, используя props.navigation.goBack(null).

Но я не могу найти код для поиска количества экранов в стеке. Я не хочу использовать устаревший навигатор!

Ответ 1

Этот пример покажет вам обратную навигацию, которая, как правило, ожидается в большинстве потоков. Вам нужно будет добавить следующий код на каждый экран в зависимости от ожидаемого поведения. Есть 2 случая: 1. Если на стеке больше 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;
}

Важно: Не забудьте связать метод в конструкторе и удалить прослушиватель в компонентеWillUnmount.

Случай 2: выход из приложения

В этом случае нет необходимости обрабатывать что-либо на этом экране, где вы хотите выйти из приложения.

Важно: это должен быть только экран на стеке.

Ответ 2

В случае, когда в стеке помещено более одного экрана, поведение кнопки обратной связи по умолчанию в реакции-native - это переход к предыдущему экрану в стеке. При обращении к кнопке возврата устройства нажмите, когда для выхода из приложения требуется только один экран, требуется специальная настройка. Тем не менее это может быть достигнуто без необходимости добавлять обратный код обработки на каждый экран, изменяя метод getStateForAction для конкретного маршрутизатора StackNavigator.

Предположим, у вас есть следующий 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 становится 0 только тогда, когда в стеке есть один экран.

Ответ 3

Guyz, пожалуйста, поймите, что это может быть не только проблема с реагированием на родной язык. Будьте осторожны, интегрируя его с firebase. Недавняя версия firebase имеет проблему интеграции кнопки "Назад" в приложениях для реагирования! Пожалуйста, понизите версию firebase до версии firebase версии 5.0.3, а затем перепроверьте, работает ли она или нет! У меня была такая же проблема, и я беспокоился в течение нескольких дней. Я, наконец, понизился до версии 5.0.3, и теперь кнопка "Назад" работает отлично! Если вы все еще сталкиваетесь с проблемой, вы можете понизить ее до более низких версий.

Ответ 4

попробуй это реагировать на навигацию

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


    handleBackButton = () => {

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

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

текущим экраном является "DefaultSelections", при нажатии кнопки "Назад" он будет переключен на то же самое, и, следовательно, кнопка "Назад" отключена, как отключение кнопки "Назад" с помощью

return true

для backButton (в соответствии с официальными документами) отключает кнопку возврата на всех экранах; нежелательный

Ответ 5

Я на v0.46.0 реагировать-родной и имел ту же проблему. Я отследил проблему до этого файла в базе данных, основанной на реакции

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

При работе с хром-отладчиком отключена линия

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

всегда возвращает пустой массив для подписки, что в свою очередь заставляет переменную invokeDefault оставаться верной и вызывать функцию.exitApp().

После дальнейшего расследования, я думаю, проблема была обнаружена и обсуждена в следующем PR # 15182.

Даже после копирования/вставки изменения PR в более старой версии RN это не сработало, скорее всего, из-за проблемы, описанной в 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();
  }
});

Просто используя.forEach, которая была оригинальной реализацией на PR до того, как исправленный синтаксис Array.from работает повсюду.

Таким образом, вы можете форк-реагировать и использовать модифицированную версию, представить PR, хотя я предполагаю, что потребуется немного времени, чтобы быть одобренным и объединенным вверх по течению, или вы можете сделать что-то похожее на то, что я сделал, чтобы переопределить RCTDeviceEventEmitter.addListener(...) для события 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 /> }
}

Ответ 6

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

Ответ 7

Я испробовал все решения, но мое приложение не вызывает даже прослушиватель обратного обработчика. Он просто существует при нажатии кнопки возврата Android. Я использую Firebase в этих приложениях.

Реактивная версия: 0.55.4

Ответ 8

Вот как я успешно реализовал, используя определенное условие:

componentWillMount() {
    BackHandler.addEventListener(
      'hardwareBackPress',
      this.handleBackButtonClick,
    );
  }

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

  handleBackButtonClick = () => {
    //some condition
    if (this.state.isSearchBarActive) {
      this.setState({
        isSearchBarActive: false,
      });
      this.props.navigation.goBack(null);
      return true;
    }
    return false;
  };

Ответ 9

Я использовал флюс для навигации.

    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;