Overview
Implementation required in 4 steps (contents extracted from medium.com):
- Implement custom ViewManager (Android)
- Register the ViewManager (Android)
- Implement the JavaScript module
- Register the module (Android)
How-to create Android component for React Native
Implementation required in 4 steps (contents extracted from medium.com):
a. Create the ViewManager subclass.
b. Implement the createViewInstance method
c. Expose view property setters using @ReactProp (or @ReactPropGroup) annotation
public class VitamioViewManager extends SimpleViewManager<VideoView> {
public static final String REACT_CLASS = “RCTVitamioView”;
@Override
public String getName() {
return REACT_CLASS;
}
...
private ThemedReactContext mContext = null;
private Activity mActivity = null;
@Override
public VideoView createViewInstance(ThemedReactContext context){
mContext = context;
return new VideoView(context);
}
...
Properties that are to be reflected in JavaScript needs to be exposed as setter method annotated with @ReactProp (or @ReactPropGroup).
Example, expose setStreamUrl setter using ReactProp:
@ReactProp(name = "streamUrl")
public void setStreamUrl(VideoView view, @Nullable String streamUrl) {
if (!LibsChecker.checkVitamioLibs(mActivity))
return;
view.setVideoPath(streamUrl);
view.setMediaController(new MediaController(mContext));
view.requestFocus();
}
...
Because LibsChecker requires an instance of Activity we will receive it via constructor, it will reference root activity used for RN application;
public VitamioViewManager(Activity activity) {
mActivity = activity;
}
...
Passing a reference to Activity into LibsChecker
public class VideoViewDemo extends Activity {
@Override public void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (!LibsChecker.checkVitamioLibs(this))
return;
setContentView(R.layout.videoview);
mEditText = (EditText) findViewById(R.id.url);
mVideoView = (VideoView) findViewById(R.id.surface_view);
if (path == "") { return; }
mVideoView.setVideoPath(path);
mVideoView.setMediaController(new MediaController(this));
mVideoView.requestFocus();
}
...
}
This package will be imported for module registration.
...
public class VitamioViewPackage implements ReactPackage {
private Activity mActivity = null;
public VitamioViewPackage(Activity activity) {
mActivity = activity;
}
@Override
public List<ViewManager>
createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new VitamioViewManager(mActivity) // <--- register the ViewManager
);
}
@Override
public List<NativeModule>
createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
}
In order to expose custom UI component in JavaScript it is necessary to call special requireNativeComponent
function, it commonly takes two parameters:
var { requireNativeComponent, PropTypes } = require('react-native');
var iface = {
name: 'VideoView',
propTypes: {
streamUrl: PropTypes.string
}
};
module.exports = requireNativeComponent('RCTVitamioView', iface);
Although it’s not mentioned as required step in official documentation we need it because of reference to the root activity:
package com.vitamio_demo;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import java.util.Arrays;
import java.util.List;
import com.sejoker.VitamView.VitamioViewPackage; // <--- import
public class MainActivity extends ReactActivity {
/**
* A list of packages used by the app. If the app uses additional views
* or modules besides the default ones, add more packages here.
*/
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new VitamioViewPackage(this) // <------ register module
);
}
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "vitamio_demo";
}
/**
* Returns whether dev mode should be enabled.
* This enables e.g. the dev menu.
*/
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
}
$ npm i react-native-android-vitamio --save
var VitamioView = require('react-native-android-vitamio');
class VideoScreen extends React.Component {
render() {
return (
<View>
<VitamioView style={styles.video} streamUrl="rtmp://fms.12E5.edgecastcdn.net/0012E5/mp4:videos/8Juv1MVa-485.mp4"/>
</View>
);
}
}
var styles = StyleSheet.create({
video: {
flex: 1,
flexDirection: 'row',
height: 400,
}
})
module.exports = VideoScreen;
When a native event occurs the native code should issue an event to the JavaScript representation of the View, and the two views are linked with the value returned from the getId()
method.
class MyCustomView extends View {
...
public void onReceiveNativeEvent() {
WritableMap event = Arguments.createMap();
event.putString("message", "MyMessage");
ReactContext reactContext = (ReactContext)getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
getId(),
"topChange",
event);
}
}
The event name topChange maps to the onChange callback prop in JavaScript (mappings are in UIManagerModuleConstants.java). This callback is invoked with the raw event, which we typically process in the wrapper component to make a simpler API:
class MyCustomView extends React.Component {
constructor(props) {
super(props);
this._onChange = this._onChange.bind(this);
}
_onChange(event: Event) {
if (!this.props.onChangeMessage) {
return;
}
this.props.onChangeMessage(event.nativeEvent.message);
}
render() {
return <RCTMyCustomView {...this.props} onChange={this._onChange} />;
}
}
MyCustomView.propTypes = {
/**
* Callback that is called continuously when the user is dragging the map.
*/
onChangeMessage: React.PropTypes.func,
...
};
var RCTMyCustomView = requireNativeComponent(`RCTMyCustomView`, MyCustomView, {
nativeOnly: {onChange: true}
});
Alternatively, you can use RCTDeviceEventEmitter.class emit
method to broadcast to all subscribers at the react-native.
reactContext.getJSModule(
DeviceEventManagerModule.RCTDeviceEventEmitter.class
).emit("CustomEventName", eventData);
class CustomReactView extends Component {
constructor(props) {
super(props);
this._onChange = this._onChange.bind(this);
}
componentWillMount() {
DeviceEventEmitter.addListener('CustomEventName', this._onChange);
}
componentWillUnmount() {
DeviceEventEmitter.removeListener('CustomEventName', this._onChange);
}
_onChange(event: Event) {
// Handle event data
}
render() {
return <OSMDroidMapView {...this.props} onChange={this._onChange}/>
}
}