はじめに
マルチプラットフォーム開発していくなかで、React Nativeですべて完結できるとは考えておらず、Native機能を使う機会はありそうなので、Android (Java) + iOS(Swift)のNative Moduleをどのように呼び出す調べてみました。
すごくシンプルに、インクリメントとデクリメントの2つの関数を 持つカウンターをネイティブ側に実装していきます。
ベースとなるプロジェクトは、下記のコマンドで作成されたものです。
npx react-native init AwesomeTSProject --template react-native-template-typescript
TypeScript側の変更部分コード
今回は例として、Increment側はCallbackを使い、Decrement側はPromiseで実装しています。 ReactはHooksを使い実装しています。
const App = () => {
// ↓----追加----↓
const [count, setCount] = useState(0);
const [errorText, setErrorText] = useState('');
const onPressIncrement = () => {
const callback = (incrementedCount: number) => {
setCount(incrementedCount);
setErrorText('');
};
NativeModules.Counter.increment(count, callback);
};
const onPressDecrement = () => {
NativeModules.Counter.decrement(count)
.then((decrementedCount: number) => setCount(decrementedCount))
.catch((error: any) => {
setErrorText(error.message);
});
};
// ↑----追加----↑
return (
<>
(省略)
// ↓----追加----↓
<Text style={styles.padding}>{count}</Text>
<Text style={styles.waringText}>{errorText}</Text>
<Button
onPress={onPressIncrement}
title="Increment(+)"
style={styles.padding}
/>
<Button
onPress={onPressDecrement}
title="Decrement(-)"
style={styles.padding}
/>
// ↑----追加----↑
</View>
</ScrollView>
</SafeAreaView>
</>
);
};
const styles = StyleSheet.create({
scrollView: {
backgroundColor: Colors.lighter,
},
(省略)
// ↓----追加----↓
padding: {
padding: 10,
},
waringText: {
fontSize: 24,
fontWeight: '600',
color: 'red',
},
// ↑----追加----↑
});
AndroidのNative Module
Native Modules Android ※公式に細かく書かれていますが、ここではコードをメインに書きます。
新規作成するクラス
- CounterModule
- CounterPackage
変更するクラス
- MainApplication
CounterModule
package com.learningreactnative;
import android.util.Log;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class CounterModule extends ReactContextBaseJavaModule {
public CounterModule(ReactApplicationContext reactApplicationContext) {
super(reactApplicationContext);
}
@ReactMethod
public void increment(int origin, Callback callback) {
int count = origin + 1;
Log.d("DEBUG", String.format("---------- count is (%s)", count));
callback.invoke(count);
}
@ReactMethod
public void decrement(int origin, Promise promise) {
if (origin == 0) {
promise.reject("E_COUNT", "You can't decrement any more!!");
} else {
int count = origin - 1;
Log.d("DEBUG", String.format("---------- count is (%s)", count));
promise.resolve(count);
}
}
@NonNull
@Override
public String getName() {
return "Counter";
}
}
IncrementとDecrementは現在のカウンターの数(Origin)とCallback or Promiseの2つの引数を受け取る。
CounterPackage
package com.learningreactnative;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CounterPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new CounterModule(reactContext));
return modules;
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
今回はView部分は作成しないので emptyList()
上で作成した、Moduleを配列に追加。
MainApplication
React Nativeから呼び出せるようにPackage追加
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
+ packages.add(new CounterPackage());
return packages;
}
iOSのNative Module
React Nativeプロジェクト内のiOSコードはObjective-Cで構成されています。 ただ、現在はObjective-Cで書くメリットはなく、Swiftでコードを書くほうが 可読性も高いので、Objective-CからSwiftコードを呼び出す形で実装していきます。
追加ファイルは、/ios直下に配置しないとコンパイルエラーとなります。
新規作成クラス
- ios/Counter.m
- ios/Counter.swift
- ios/AwesomeTSProject-Bridging-Header.h
ios/Counter.m
Swiftで書いたコードをReact Native側から実行できるIF Objective-Cの文法で記載。
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
@interface RCT_EXTERN_MODULE(Counter, NSObject)
RCT_EXTERN_METHOD(increment: (NSInteger)origin withCallback:(RCTResponseSenderBlock)callback)
RCT_EXTERN_METHOD(decrement: (NSInteger)origin withResolve:(RCTPromiseResolveBlock)resolve withReject:(RCTPromiseRejectBlock)reject)
@end
ios/Counter.swift
incrementと、decrementの関数を追加する。
import Foundation
@objc(Counter)
class Counter: NSObject {
static func requiresMainQueueSetup() -> Bool {
return false
}
@objc
func increment(_ origin: Int, withCallback callback: RCTResponseSenderBlock) -> Void {
callback([origin + 1])
}
@objc
func decrement(_ origin: Int, withResolve resolve: RCTPromiseResolveBlock, withReject reject: RCTPromiseRejectBlock) -> Void{
if(origin <= 0){
reject("E_COUNT", "You can't decrement any more!!", nil)
return
}
resolve(origin - 1)
}
}
ios/AwesomeTSProject-Bridging-Header.h
Swiftコードから、RCTBridgeModuleを呼び出せるようにするIFファイル
#import "React/RCTBridgeModule.h"