React Nativeでネイティブの機能を使いたい!って時はありませんか?
しかし、GitHubからお目当てのサードパーティーがない。バグだらけでメンテされてない。など、どうしてもネイティブ側も書かないといけない。みたいな状況が発生します。
そんな時に役立つ、ネイティブの機能をReact(js)側に橋渡しする方法を共有したいと思います。
TL;DR
- https://github.com/react-native-community/react-native-camera が題材
- 直接的にAndroidの機能を使うモジュールクラスを作成
- 作成したモジュールをReact側で扱うための、ReactPackageインタフェースをimplementしたパッケージクラスを作成
AndroidのNative機能を使う際、音声やバックグラウンド処理などのViewがない機能とカメラのプレビューのようなViewありきの機能がある。
ViewがあるかないかでReactに値を渡すメソッドが違うので、ここでは分けて考える。
共通部分
ReactPackageインタフェースをimplementsしたクラスを実装する
- view以外の機能を提供するNativeModuleを、React側で使用するために登録する
- Nativeが提供するUI ComponentをReact側で使用するためにviewManagerを登録する
- createJSModulesは現状必須でない
RNCameraPackage.java
package ...
import ...
public class RNCameraPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
return Arrays.<NativeModule>asList(
new RCTCameraModule(reactApplicationContext),
new CameraModule(reactApplicationContext),
new FaceDetectorModule(reactApplicationContext)
);
}
// Deprecated in RN 0.47
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
return Arrays.<ViewManager>asList(
new RCTCameraViewManager(),
new CameraViewManager()
);
}
}
UI以外のNative機能をブリッジしたい場合
ReactContextBaseJavaModuleを継承したモジュールを実装
-
android/app/src側のjavaファイルにて、
ReactContextBaseJavaModule
クラスを継承したクラスをつくる。 -
Module名を書く
-
React側で使う定数を定義する(なければなしで良い)
-
React側で使うメソッドを定義する。この際、@ReactMethodアノテーションを付与する。
CameraModule.java
//①
public class CameraModule extends ReactContextBaseJavaModule {
private static final String TAG = "CameraModule";
...
public CameraModule(ReactApplicationContext reactContext) {
super(reactContext);
mScopedContext = new ScopedContext(reactContext);
}
...
//②
@Override
public String getName() {
return "RNCameraModule";
}
//③
@Override
public Map<String, Object> getConstants() {
return Collections.unmodifiableMap(new HashMap<String, Object>() {
{
put("Type", getTypeConstants());
put("FlashMode", getFlashModeConstants());
...
}
});
}
...
//④
@ReactMethod
public void takePicture(final ReadableMap options, final int viewTag, final Promise promise) {
final ReactApplicationContext context = getReactApplicationContext();
final File cacheDirectory = mScopedContext.getCacheDirectory();
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
uiManager.addUIBlock(new UIBlock() {
@Override
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
RNCameraView cameraView = (RNCameraView) nativeViewHierarchyManager.resolveView(viewTag);
try {
if (cameraView.isCameraOpened()) {
cameraView.takePicture(options, promise, cacheDirectory);
} else {
promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running");
}
} catch (Exception e) {
promise.reject("E_CAMERA_BAD_VIEWTAG", "takePictureAsync: Expected a Camera component");
}
}
});
}
...
}
ReactMethodを書く際に、JSからの引数が存在し、型がObjectやArrayの場合
Object => ReadableMap
Array => ReadableArray
で型の置き換えを行い、引数を使う。
また、ReactMethodで扱う引数はすべてを使う。
UI系のNative機能をブリッジしたい場合
-
com.facebook.react.uimanager.ViewGroupManager
をimport -
ViewGroupManager
を継承したクラスを作成 - getName()をOverrideしてModule名を決める
-
createViewInstanceOverride
して、自分が使いたいUI Componentを記述して、そのコンポーネントを返すメソッドを作成
CameraViewManager.java
package com.example;
//①
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
//②
public class CameraViewManager extends ViewGroupManager<RNCameraView> {
private static final String REACT_CLASS = "RNCamera";
@Override
public void onDropViewInstance(RNCameraView view) {
view.stop();
super.onDropViewInstance(view);
}
//③
// Component name that will be called from JavaScript
@Override
public String getName() {
return REACT_CLASS;
}
//④
// Return the view component instantiated with Activity context
@Override
public TextView createViewInstance(ThemedReactContext reactContext) {
return new RNCameraView(themedReactContext);
}
}
JS側でNativeModuleを使う
- NativeModule.{getName()が返す値}を呼び出す。(ここでは別ファイルにして使えるように
RNCamera.js
でexport.
OriginalCamera.js
import { NativeModules } from `react-native`;
export default NativeModules.RNCameraModule;