React Native ブリッジ書き方　~ Android編 ~

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したクラスを実装する

1. view以外の機能を提供するNativeModuleを、React側で使用するために登録する
2. Nativeが提供するUI ComponentをReact側で使用するためにviewManagerを登録する 
3. 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を継承したモジュールを実装

1. android/app/src側のjavaファイルにて、`ReactContextBaseJavaModule`クラスを継承したクラスをつくる。

2. Module名を書く

3. React側で使う定数を定義する（なければなしで良い）

4. 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で扱う引数はすべて<final>を使う。



# UI系のNative機能をブリッジしたい場合
1. `com.facebook.react.uimanager.ViewGroupManager`をimport
2. `ViewGroupManager`を継承したクラスを作成
3. getName()をOverrideしてModule名を決める
4. `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を使う
1. NativeModule.{getName()が返す値}を呼び出す。(ここでは別ファイルにして使えるように`RNCamera.js`でexport.

```OriginalCamera.js
import { NativeModules } from `react-native`;

export default NativeModules.RNCameraModule;
```