機能
-
Native Modules
出来ること
・関数の作成
・イベントの通知
・コールバック -
Native UI Components
出来ること
・Viewの作成
・Viewのイベントの通知
Native Modules
ダイアログを表示するModuleを例にします。
[機能]
1, Androidのダイアログを表示する。(関数の作成)
2, ダイアログの"OK"ボタンがクリックされたらコンソールに"true"が表示される。(イベントの通知)
3, "DATE"ボタンがクリックされたら現在時刻が表示される。(コールバック)
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のイベントの通知)
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を以下のように登録します
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
に登録します
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// packageが増えるたびに追加していく
packages.add(new ExamplePackage());
return packages;
}
RN側
Native Modules
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
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から呼びたい場合
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 とかの話」です