ReactNative AndroidのBridge解説

## 機能
- Native Modules
出来ること
・関数の作成
・イベントの通知
・コールバック

- Native UI Components
出来ること
・Viewの作成
・Viewのイベントの通知

## Native Modules
ダイアログを表示するModuleを例にします。

**[機能]**
1, Androidのダイアログを表示する。（関数の作成）
2, ダイアログの"OK"ボタンがクリックされたらコンソールに"true"が表示される。（イベントの通知）
3, "DATE"ボタンがクリックされたら現在時刻が表示される。（コールバック）


```java:DialogModule.java
public class DialogModule extends ReactContextBaseJavaModule {
    // コンストラクター
    public DialogModule(@NonNull ReactApplicationContext reactContext) {
        super(reactContext);
    }
   // このモジュールを呼び出すためのタグのようなもの
   @Override
    public String getName() {
        return "DialogModule";
    }

    // RNで使用出来るクラス定数を定義出来る
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put("SINGLE_BUTTON", "SINGLE");
        constants.put("DOUBLE_BUTTON", "DOUBLE");
        return constants;
    }

    // ダイアログを表示する関数
    @ReactMethod
    public void showDialog(String message, String type) {
        if (type.equals("SINGLE")) {
            new AlertDialog.Builder(getCurrentActivity())
                    .setTitle("DialogModule")
                    .setMessage(message)
                    .setPositiveButton("CLOSE", (dialog, which) -> {
                        dialog.dismiss();
                    })
                    .show();
        } else {
            new AlertDialog.Builder(getCurrentActivity())
                    .setTitle("DialogModule")
                    .setMessage(message)
                    .setPositiveButton("OK", (dialog, which) -> {
                        sendEvent();
                    })
                    .setNegativeButton("CLOSE", (dialog, which) -> {
                        dialog.dismiss();
                    })
                    .show();
        }
    }

    // 現在時刻を表示してコールバックする関数
    @ReactMethod
    private void getCurrentTime(Callback callback) {
        Calendar calendar = Calendar.getInstance();
        callback.invoke(calendar.getTime().toString());
    }

    // ボタンがクリックされたことを通知するイベント
    private void sendEvent() {
        WritableMap params = Arguments.createMap();
        params.putBoolean("click", true);
        getReactApplicationContext()
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit("onClick", params);
    }
}
```
### 解説
#### `getName`
RNから呼び出す際の文字列を"DialogModule"のように記述する必要があります。

```java
@Override
public String getName() {
    return "DialogModule";
}
```
#### `getConstants`
RNから使用できるMolueのクラス定数を設定できます。
※実装は必須ではありません。
`constants.put(定数名, 値);`

```java
public Map<String, Object> getConstants() {
    final Map<String, Object> constants = new HashMap<>();
    constants.put("SINGLE_BUTTON", "SINGLE");
    constants.put("DOUBLE_BUTTON", "DOUBLE");
    return constants;
}
```
#### `@ReactMethod`
RNから使用できるメソッドを設定できます。
コールバックを実行する場合は`.invoke()`で実行します。

```java
@ReactMethod
private void getCurrentTime(Callback callback) {
    Calendar calendar = Calendar.getInstance();
    callback.invoke(calendar.getTime().toString());
}
```

`イベント(リスナー)`
RNに登録できるイベントを設定できます。

```java
// コールバックの値を設定
WritableMap params = Arguments.createMap();
params.putBoolean("click", true);
// イベント名とコールバックの値を設定
getReactApplicationContext()
    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
    .emit("onClick", params);
```

## Native UI Components
動画playerを表示するViewを例にします。

**[機能]**
1, 動画playerを表示する。（Viewの作成）
2, urlをRN側からpropsで受け取り動画を再生する。
3, 再生終了時にログを表示する。（Viewのイベントの通知）

```VideoViewManager.java
public class VideoViewManager extends SimpleViewManager<VideoView> {
    private Context context;

    // このモジュールを呼び出すためのタグのようなもの
    @Override
    public String getName() {
        return "VideoView";
    }

    // 使用するViewのインスタンを返すコンストラクターのようなもの
    @Override
    protected VideoView createViewInstance(ThemedReactContext reactContext) {
        this.context = reactContext;
        return new VideoView(reactContext);
    }

    // Propsで受け取った値で処理をする関数
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    @ReactProp(name="url")
    public void setVideoPath(VideoView videoView, String urlPath) {
        Uri uri = Uri.parse(urlPath);
        videoView.setMediaController(new MediaController(context));
        videoView.setVideoURI(uri);
        // 再生準備出来次第再生する
        videoView.setOnPreparedListener(mp -> {
            videoView.start();
        });
        // 再生終了時にpropsの『onFinish』にコールバックする。(通知)
        videoView.setOnCompletionListener(mp -> {
            ReactContext reactContext = (ReactContext)context;
            
            WritableMap event = Arguments.createMap();
            event.putString("message", "onDirectEvent");
            reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onDirectEvent",event);

            WritableMap event2 = Arguments.createMap();
            event2.putString("message", "onBubblingEvent");
            reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2);
        });
        videoView.getDuration();
    }

    @Override
    public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
        return MapBuilder.<String, Object>builder()
                .put("onDirectEvent", MapBuilder.of("registrationName", "onDirectEvent"))
                .build();
    }

    @Override
    public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
        return MapBuilder.<String, Object>builder()
                .put("onBubblingEvent", MapBuilder.of("phasedRegistrationNames",
                                    MapBuilder.of(
                                            "bubbled", "onBubble",
                                            "captured", "onCapture")))
                .build();
    }
}
```
### 解説
#### `getName`
RNから呼び出す際の文字列を"VideoView"のように記述する必要があります。

```java
@Override
public String getName() {
    return "VideoView";
}
```
#### `@ReactProp`
ViewのPropsを受け取るセッターメソッド
メソッド名自体はなんでもいいです
`@ReactProp(name="props名")`
`public void setProp(Viewの型 view, 型 Propsの値)`


```java
@ReactProp(name="url")
public void setVideoPath(VideoView videoView, String urlPath) {
        // ...
}
```
#### `createViewInstance`
Viewのインスタンスを返す関数
※必須です

```java
@Override
protected VideoView createViewInstance(ThemedReactContext reactContext) {
    return new VideoView(reactContext);
}
```

`イベントの通知`
Propsのコールバックを設定できます。
#### イベントの登録
イベントを登録するメソッドは２つあります。
・getExportedCustomDirectEventTypeConstants
・getExportedCustomBubblingEventTypeConstants

#### `getExportedCustomDirectEventTypeConstants`
1つのイベントで1つのpropsに通知する。
登録方法
`MapBuilder.builder().put("イベント名", MapBuilder.of("registrationName", "プロップス名")).build();`



#### `getExportedCustomBubblingEventTypeConstants`
1つのイベントで2つのpropsに通知することができる。
登録方法
`MapBuilder.builder().put("イベント名", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "プロップス名１","captured", "プロップス名２"))).build();`


```java
// イベントを登録する関数１
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
    return MapBuilder.<String, Object>builder()
            .put("onDirectEvent", MapBuilder.of("registrationName", "onDirectEvent"))
            .build();
}

// イベントを登録する関数２
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
    return MapBuilder.<String, Object>builder()
            .put("onBubblingEvent", MapBuilder.of("phasedRegistrationNames",
                                MapBuilder.of(
                                        "bubbled", "onBubble",
                                        "captured", "onCapture")))
            .build();
}
```
通知したい箇所で`receiveEvent`を呼び出す
`receiveEvent（"viewのID", "イベント名", コールバックの値）`

```java
// ...
ReactContext reactContext = (ReactContext)context;
// イベントを通知する処理１ "onDirectEvent"イベント
WritableMap event = Arguments.createMap();
event.putString("message", "onDirectEvent");
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onDirectEvent",event);

// イベントを通知する処理２ "onBubblingEvent"イベント
WritableMap event2 = Arguments.createMap();
event2.putString("message", "onBubblingEvent");
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2);
// ...
```
RN側では以下のようにイベントが通知される

```jsx
<VideoView
      onDirectEvent={({ nativeEvent }) => console.log(nativeEvent.message)} // onDirectEvent
      onCapture={({ nativeEvent }) => console.log(nativeEvent.message)} // onBubblingEvent
      onBubble={({ nativeEvent }) => console.log(nativeEvent.message)} // onBubblingEvent
/>
```

## ModuleとUI Componentの登録
作成したModuleとUI Componentを以下のように登録します

```ExamplePackage.java
public class ExamplePackage implements ReactPackage {
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.<ViewManager>singletonList(
                // UI Componentが増えるたびに追加していく
                new VideoViewManager()
        );
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext
            return Collections.<ViewManager>singletonList(
                // Moduleが増えるたびに追加していく
                new DialogModule(reactContext)
        );
    }
}
```
作成したpackageをMainApplication.java内の`getPackages`に登録します

```MainApplication.java
@Override
protected List<ReactPackage> getPackages() {
      List<ReactPackage> packages = new PackageList(this).getPackages();
           // packageが増えるたびに追加していく
           packages.add(new ExamplePackage());
      return packages;
}
```

RN側
### [Native Modules](#nativemodules)

```Dialog.jsx
import React from 'react';
import { NativeModules } from 'react-native';
// getName関数の返り値("DialogModule")を指定
DialogModule = NativeModules.DialogModule;

const Dialog = () => {
  const [date, setDate] = React.useState("");

  React.useEffect(() => {
    const eventEmitter = new NativeEventEmitter(DialogModule);
    eventEmitter.addListener('onClick', (event) => {
       console.log(event.change) // "true"
    });
  }, [])

  return (
    <>
      <TouchableOpacity onPress={() => DialogModule.showDialog('SINGLE BUTTON', DialogModule.SINGLE_BUTTON)}>
        <Text>SINGLE BUTTON Dialog</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => DialogModule.showDialog('DOUBLE BUTTON', DialogModule.DOUBLE_BUTTON)}>
        <Text>DOUBLE BUTTON Dialog</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={() => DialogModule.getCurrentTime(time => setDate(time))}>
        <Text>DATE</Text>
      </TouchableOpacity>
        <Text>CURRENT DATE:</Text>
      <Text>[ {date} ]</Text>
    </>
  )
}

export default Dialog
```
### [Native UI Components](#nativeuicomponents)

```VideoView.jsx
import React from 'react';
import { requireNativeComponent } from 'react-native';
VideoView = requireNativeComponent('VideoView');

const VideoView = () => {

  return (
    <>
      <VideoView 
        style={{ width: '100%', height: '100%' }}
        url="https://www.radiantmediaplayer.com/media/bbb-360p.mp4"
        onDirectEvent={({ nativeEvent }) => console.log(nativeEvent.message)}
        onCapture={({ nativeEvent }) => console.log(nativeEvent.message)}
        onBubble={({ nativeEvent }) => console.log(nativeEvent.message)} />
    </>
  )
}

export default VideoView
```


## おまけ
UI Componentに実装してある関数をModuleから呼びたい場合

```ExamplePackage.java
public class ExamplePackage implements ReactPackage {
    private ExampleViewManager instance;
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.<ViewManager>singletonList(
                instance
        );
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext
            instance = new ExampleViewManager();
            return Collections.<ViewManager>singletonList(
                // モジュールにViewManagerのインスタンスを渡す
                // あとはモジュール内ので定義した関数からインスタンスのメソッドを呼べば良い
                new ExampleModule(reactContext, instance)
        );
    }
}
```


## 最後に
Androidのブリッジ部分については英語の記事ばかりかつ動かないサンプルコードが多く苦労しました。
記事書く時間が少なかったので、抜けてるところや間違った箇所があるかもしれないので記事の内容は参考程度にしてください。

続きまして、React Native Advent Calendar 20日目の記事は @duka さんの「AB test とかの話」です :tada: