react-navigation/docs/guides/Hybrid-Navigation.md
2017-01-26 11:53:33 -08:00

4.1 KiB

Common React Navigation API - Hybrid Integration

This is a purely speculative API that demonstrates how it may be possible to integrate the JS navigation API in a hybrid app.

Setting up a screen

It should be possible to register new screens from JS into native. In your main bundle:

const HybridNavigationModule = require('NativeModules').HybridNavigation;

HybridNavigationModule.registerScreens([
  {
    type: 'Marketplace',
    screen: MarketplaceScreen,
  },
  {
  	type: 'Product',
  	screen: ProductScreen,
  },
]);

Linking to JS

Now, your native code can open a react screen by type name:

// please pretend this is Obj-C or Java syntax:
CoreHybridNavigation.openReactScreen('Profile', {id: 123});

Linking to Native

If JS product code wants to request navigation to a screen that may or may not be in native, it can do this:

const MarketplaceScreen = ({ navigation }) => (
  <View>
    <Button onPress={() => navigation.dispatch({
      type: 'Product',
      id: 42,
    })}>
      See product 42
    </Button>
  </View>
);

Inside the infra:

class InfraScreen extends React.Component {
  constructor() {
    const {initURI, type} = this.props;
    const ScreenView = ScreenRegistry[type].screen;
    const router = ScreenView.router;
    const deepLinkAction = router.getActionForURI(initURI);
    const initAction = deepLinkAction || {type: 'init'}
    const nav = router.getStateForAction(initAction);
    this.state = {
      nav,
    };
    HybridNavigationModule.setNavOptions(this.state.nav);
  }
  componentWillUpdate() {
    HybridNavigationModule.setNavOptions(this.state.nav);
  }
  dispatch = (action) => {
    const {type} = this.props;
    const ScreenView = ScreenRegistry[type].screen;
    const {getStateForAction} = ScreenView.router;
    const newNavState = getStateForAction(action, this.state.nav);
    if (newNavState !== this.state.nav) {
      this.setState({ nav: newNavState });
      return true;
    }
    if (action.type === 'URI') {
      HybridNavigationModule.openURI(action.uri);
      return true;
    }
    if (action.type === 'Back') {
      HybridNavigationModule.goBack();
      return true;
    }
    HybridNavigationModule.openAction(action);
    return true;
  }
  render() {
    const {type} = this.props;
    const ScreenView = ScreenRegistry[type].screen;
    const navigation = {
      dispatch: this.dispatch,
      state: this.state.nav,
    };
    return <ScreenView navigation={navigation} />;
  }
}

Setting title

MarketplaceScreen.router = {
  getStateForAction(action, lastState) {
    return lastState || {title: 'Marketplace Home'};
  },
};

A HOC could be used to make this feel more elegant.

Disabling/Enabling the right button

const TestScreen = ({ navigation }) => (
  <View>
    <Button onPress={() => navigation.dispatch({
      type: 'ToggleMyButtonPressability',
    })}>
      {navigation.state.rightButtonEnabled ? 'Disable' : 'Enable'} right button
    </Button>
    <Text>Pressed {navigation.state} times</Text>
  </View>
);
TestScreen.router = {
  getStateForAction(action, lastState = {}) {
    let state = lastState || {
      rightButtonEnabled: true,
      rightButtonTitle: 'Tap Me',
      pressCount: 0,
    };
    if (action.type === 'ToggleMyButtonPressability') {
      state = {
        ...state,
        rightButtonEnabled: !state.rightButtonEnabled,
      };
    } else if (action.type === 'RightButtonPress') {
      state = {
        ...state,
        pressCount: state.pressCount + 1,
      };
    }
    return state;
  },
};

Before JS starts

A JSON file could be defined for native to consume before JS spins up:

{
  "screens": [
    {
      "type": "Profile",
      "path": "/users/:id?name=:name",
      "params": {
        "name": "string",
        "id": "number"
      },
      "title": "%name%' s Profile",
      "rightButtonTitle": "Message %name%"
    },
    {
      ...
    }
  ]
}

This seems like a pain to set up, so we can statically analyze our JS and autogenerate this JSON! If the JS in an app changes, there could be a way for JS to report the new routing configuration to native for use on the next cold start.