Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing UISplitViewController using React Navigation

I need to implement tablet support in the React Native app I am working on. We decided that going with UISplitViewController makes the most sense for us, but React Navigation (https://reactnavigation.org) does not have any support for it.

I tried solving this problem in a straightforward way by putting 2 navigators side-by-side in a view and modifying their getStateForAction to open certain screens in the details view:

export interface Props {
  MasterNavigator: NavigationContainer;
  DetailNavigator: NavigationContainer;
  detailRouteNames: string[];
}

export class MasterDetailView extends React.Component<Props> {
  masterNavigator: any;
  detailNavigator: any;

  componentDidMount() {
    const { MasterNavigator, DetailNavigator, detailRouteNames } = this.props;
    const defaultMasterGetStateForAction = MasterNavigator.router.getStateForAction;

    MasterNavigator.router.getStateForAction = (action: any, state: any) => {
      if (action.type === NavigationActions.NAVIGATE && detailRouteNames.indexOf(action.routeName) !== -1) {
        action.params.isRootScreen = true;
        this.detailNavigator.dispatch(NavigationActions.reset({ index: 0, actions: [action] }));

        return state;
      }

      return defaultMasterGetStateForAction(action, state);
    };

    const defaultDetailGetStateForAction = DetailNavigator.router.getStateForAction;

    DetailNavigator.router.getStateForAction = (action: any, state: any) => {
      if (action.type === NavigationActions.BACK && state.routes.length === 1) {
        this.masterNavigator.dispatch(NavigationActions.back());
        return null;
      }

      return defaultDetailGetStateForAction(action, state);
    };
  }

  render() {
    const { MasterNavigator, DetailNavigator } = this.props;

    return (
      <View style={styles.view}>
        <View style={styles.masterContainer}>
          <MasterNavigator ref={(mn: any) => this.masterNavigator = mn}/>
        </View>
        <View style={styles.divider}/>
        <View style={styles.detailContainer}>
          <DetailNavigator ref={(dn: any) => this.detailNavigator = dn}/>
        </View>
      </View>
    );
  }
}

The master navigator is a TabNavigator with StackNavigators in each tab, the detail navigator is a StackNavigator. Here's how it looks: result

This approach kind of works, but back button on Android behaves incorrectly. I want it to navigate back in the details navigator, then in the master navigator. It is possible to make it work with a couple of hacks, but then it becomes impossible to navigate back from the app using the back button (for some reason, nothing happens when I click it). I could try fix this by overriding the behavior of back button and dispatching back action to the master/detail navigators or closing the app, but when dispatching an action to the navigator, there is no way to know whether it responded to it or not (particularly with the master navigator, which is a TabNavigator), so it seems like I am stuck.

Are there some special considerations when using navigators this way? Is React Navigation even suitable for this use case? If not, what are the other ways to emulate UISplitViewController in React Native apps?

like image 344
Andrii Chernenko Avatar asked Oct 17 '22 18:10

Andrii Chernenko


1 Answers

The problem with incorrect back button behavior was my fault. One of the screens had a back button handler which always returned true, thus consuming all back button presses (duh!). I fixed this and added the following back button handler to the MasterDetailView:

private onPressBack = (): boolean => {
    const action = NavigationActions.back();
    return this.detailNavigator.dispatch(action) || this.masterNavigator.dispatch(action);
};

And yes, there is a way to find out whether a navigator consumed an action: the navigator.dispatch(action) returns true if the event was consumed and false otherwise.

Once the problem with back button is fixed, this setup emulates the UISplitViewController pretty well.

like image 199
Andrii Chernenko Avatar answered Oct 21 '22 01:10

Andrii Chernenko