Native Modules and Views #

If React VR doesn't support a feature that you need, you can build it yourself.

Sometimes an app needs access to a platform API that React VR doesn't have a corresponding module for yet. Maybe you want to reuse some existing JavaScript code without having to re-implement it in the React context. Or, you want to write some high performance, multi-threaded code for image processing, a database, or other advanced extensions.

This is a more advanced feature and we don't expect it to be part of the usual development process, however it is essential that it exists.

Cube Example #

One example of using Native Modules is to support interaction between a React VR UI element and a custom Three.js object that you've added to your scene, for example a geometric cube similar to the one in the Three.js README.

Setting up the Scene #

The React VR framework can handle the camera and renderer setup for you, so you only need to focus on adding objects to your scene and animating them. First, we create the scene in the init function of vr/client.js (one of the files provided as part of the Starter Project) and provide it as an option to the VRInstance constructor. We also create CubeModule, a Native Module described later in this section.

const scene = new THREE.Scene(); const cubeModule = new CubeModule(); const vr = new VRInstance(bundle, 'CubeSample', parent, { cursorVisibility: 'visible', nativeModules: [ cubeModule ], scene: scene, });

Next, we create the cube mesh and add it to the scene. We use the same Three.js operations as we normally would, with a couple modifications which are often necessary to make Three.js objects appear correctly in React VR.

  • React VR uses units of 1 meter, so we use cube dimensions of 1 instead of 100.
  • The VRInstance camera is at the origin, so we change the z position of the cube so it is visible.
const cube = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial() ); cube.position.z = -4; scene.add(cube); cubeModule.init(cube);

Above, we also initialize the cubeModule with a handle to the cube. Finally, add the per-frame update logic into the vr.render method.

vr.render = function(timestamp) { const seconds = timestamp / 1000; cube.position.x = 0 + (1 * (Math.cos(seconds))); cube.position.y = 0.2 + (1 * Math.abs(Math.sin(seconds))); };

Using the Native Module #

Let's say we want the cube to change color based on UI interaction such as a button click. Here is a Native Module that implements the changeCubeColor function, which is called asynchronously across the React Native bridge. The constructor and init methods are called directly in client.js to setup the module, as shown above.

export default class CubeModule extends Module { constructor() { super('CubeModule'); } init(cube) { this.cube = cube; } changeCubeColor(color) { this.cube.material.color = new THREE.Color(color); } }

Now we can call the changeCubeColor method from index.vr.js, for example in an onClick handler.

import NativeModules from 'react-vr'; ... const CubeModule = NativeModules.CubeModule; ... render() { ... <VrButton onClick={()=>CubeModule.changeCubeColor(hexColor)}> ... </VrButton> ..

See the CubeSample for the full code of this example.

Creating Native Views #

By implementing a native view you can control how the properties specified in your React VR code interact with the runtime code, this could be visual representation or even sound.

To create a native view you will need to import the main module react-vr-web

import * as ReactVR from 'react-vr-web';

Then create a class that extends the RCTBaseView, the constructor should create the OVRUI.UIView and register getters and setters on any properties.

The class also implements a static describe function which the runtime uses to determine which properties should be sent from the React VR code to the runtime, these are the runtime implementation of the PropTypes.

class RCTTestLight extends ReactVR.RCTBaseView { constructor(guiSys: GuiSys) { super(); const light = new THREE.AmbientLight(); this.view = new OVRUI.UIView(guiSys); this.view.add(light); Object.defineProperty( this.props, 'intensity', { set: value => { light.intensity = value; }, } ); this.props.intensity = 1; } static describe() { return merge(super.describe(), { // declare the native props sent from react to runtime NativeProps: { intensity: 'number', }, }); } }

Finally, the custom views must be registered to the React VR context and this is handled by provided a list of available customViews when creating ReactVR.VRInstance

const vr = new ReactVR.VRInstance(bundlePath, appName, document.body, { customViews: [{name: 'TestLight', view: RCTTestLight}], ...options, });

To make this TestLight component available to React VR code it is necessary to register a component which makes use of the NativeMethodsMixin. propTypes will need to have been set to enable the propTypes to be handled by the React VR core, viewConfig is used to support the Animated modules and the render function should return a component created using the requireNativeComponent utility function.

const TestLight = React.createClass({ mixins: [NativeMethodsMixin], propTypes: { ...View.propTypes, style: StyleSheetPropType(LayoutAndTransformPropTypes), intensity: PropTypes.number, }, viewConfig: { uiViewClassName: 'AmbientLight', validAttributes: { ...ReactNativeViewAttributes.RCTView, intensity: true, }, }, getDefaultProps: function() { return {}; }, render: function() { return ( <RKTestLight {...props}> </RKTestLight> ); }, }); const RKTestLight = requireNativeComponent('TestLight', TestLight, { nativeOnly: {}, }); module.exports = TestLight;

You can file an issue on GitHub if you see a typo or error on this page!