Writing Android component for React Native

Update: migrated source code to support React Native 0.19.

For whose who are not familiar with React Native, I would refer to a small guide I wrote some time ago.

In my current company, I came up with an idea to prototype Android port of our iOS application. It was just 2 weeks after React-native added support for Android and I thought it would be a fun project to play with RN.

Obviously the most challenging part of the application is handling live video stream, because it requires switching stream’s video quality based on available Internet bandwidth. But first things first — I needed a RN native component to show any video stream. There is a popular video component for RN but it has support for iOS only. I decided to write my own RN component wrapper around Vitamio player. It is well known open-source project and has support of RTMP protocol we use for mobile app.

I had no prior experience with writing native RN components so I went directly to RN documentation on how to create one. A guide I refer to is called Native UI Components, there is similar one for iOS. There are several essential parts to declare:

  • Implement custom ViewManager (Android part)
  • Register the ViewManager (Android part)
  • Implement the JavaScript module
  • Register the module (Android part)

Implement custom ViewManager

Referring to the example of declaring VideoView for Vitamio this is how the essence of VideoView declaration looks like:

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();
}
...
}

The code looks quite straightforward. Apart from passing a reference to Activity into LibsChecker, VideoView requires a path to a video stream and instance of MediaController.

Let’s start with declaring our own custom ViewManager, the code is quite similar to the reference example in docs:

...
public class VitamioViewManager extends SimpleViewManager<VideoView>{
public static final String REACT_CLASS = “RCTVitamioView”;
@Override
public String getName() {
return REACT_CLASS;
}

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();
}

add createViewInstance implementation:

private ThemedReactContext mContext = null;
private Activity mActivity = null;
@Override
public VideoView createViewInstance(ThemedReactContext context){
mContext = context;
return new VideoView(context);
}

One note about the code. 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;
}

Register the ViewManager

The final Java step is to register the ViewManager to the application, this happens via the applications package member function createViewManagers:

...
public class VitamioViewPackage implements ReactPackage {

private Activity mActivity = null;

public VitamioViewPackage(Activity activity) {
mActivity = activity;
}


@Override
public List<NativeModule>
createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
  @Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
  @Override
public List<ViewManager>
createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new VitamioViewManager(mActivity)
);
}
}

Implement the JavaScript module

In order to expose custom UI component in JavaScript it is necessary to call special requireNativeComponent function:

var { requireNativeComponent, PropTypes } = require('react-native');

var iface = {
name: 'VideoView',
propTypes: {
streamUrl: PropTypes.string
}
};

module.exports = requireNativeComponent('RCTVitamioView', iface);

Register the module

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 {

/**
* 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;
}

/**
* 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) // <------ add here
);
}
}

Example of usage

Install the package in a project:

npm i react-native-android-vitamio --save

DeclareVideoView:

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;

Thoughts

I was thrilled with the fact that I could expose iOS and Android existing components in RN application. I stumbled over a couple of issues during the development process, although I found very active community around RN and Android team is working hard on bringing parity with iOS counterpart and fixing found issues. The releases are regular and I predict RN for Android will be quite mature by the end of the year.

References