Help us understand the problem. What is going on with this article?

ReactNative AndroidのBridge解説

機能

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

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

Native Modules

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

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

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"のように記述する必要があります。

@Override
public String getName() {
    return "DialogModule";
}

getConstants

RNから使用できるMolueのクラス定数を設定できます。
※実装は必須ではありません。
constants.put(定数名, 値);

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()で実行します。

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

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

// コールバックの値を設定
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"のように記述する必要があります。

@Override
public String getName() {
    return "VideoView";
}

@ReactProp

ViewのPropsを受け取るセッターメソッド
メソッド名自体はなんでもいいです
@ReactProp(name="props名")
public void setProp(Viewの型 view, 型 Propsの値)

@ReactProp(name="url")
public void setVideoPath(VideoView videoView, String urlPath) {
        // ...
}

createViewInstance

Viewのインスタンスを返す関数
※必須です

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

イベントの通知
Propsのコールバックを設定できます。

イベントの登録

イベントを登録するメソッドは2つあります。
・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", "プロップス名1","captured", "プロップス名2"))).build();

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

// イベントを登録する関数2
@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", "イベント名", コールバックの値)

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

// イベントを通知する処理2 "onBubblingEvent"イベント
WritableMap event2 = Arguments.createMap();
event2.putString("message", "onBubblingEvent");
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(videoView.getId(),"onBubblingEvent",event2);
// ...

RN側では以下のようにイベントが通知される

<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

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

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:

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした