현재 프로젝트가 React Native 하이브리앱으로 제작되면서,
네이티브 코드를 만질 일이 많지는 않지만 후에 커스텀하기 편하도록
RN 코드와 AndroidㆍiOS 간 네이티브 모듈을 제작해보았다.
구현 방식 자체는 어렵지 않다 !
해당 내용들은 React Native 공식 홈페이지에서 캘린더 모듈을 생성하는
예제를 토대로 작성해보았다. 천천히 알아보자 🚀
안드로이드 네이티브 모듈
(1) 파일 생성
네이티브 모듈을 생성하기 위해 우선 모듈 파일과 패키지 파일을 생성해주어야 한다.
android/app/src/main/java/com/your-app-name/ folder
위 경로에 "..Module.java", "...Package.java" 의 형태로 파일을 생성한다.
아래의 예제는 캘린더 모듈을 생성하기에, 그와 관련된 이름인 CalendarModule 로 생성하였다.
(2) 모듈 클래스 구현
ReactContextBaseJavaModule 을 상속받은 클래스를 생성한다.
안드로이드의 모든 자바 모듈은 getName() 메서드를 구현해야 하고, 이는 모듈 이름을 반환해준다.
다음으로 이벤트를 생성하고 자바스크립트(React) 에서 호출할 수 있는 메서드를 네이티브 모듈에 추가한다.
자바스크립트에서 호출되는 모든 기본 모듈 메서드는 @ReactMethod 어노테이션을 태그해주면 된다.
public class NativeBridgeModule extends ReactContextBaseJavaModule {
private static ReactApplicationContext context;
NativeBridgeModule(ReactApplicationContext context) {
super(context);
this.context = context;
}
@Override
public String getName() {
return "NativeBridgeModule";
}
@ReactMethod
public void connect(String message) {
Log.d("connect", message);
}
}
추가로 아래와 같이 작성하면 동기 메서드임을 의미한다.
현재로서는 메서드를 동기적으로 호출하면 성능이 저하되므로 크게 권장하지는 않는다고 한다.
@ReactMethod(isBlockingSynchronousMethod = true)
(3) 패키지 클래스 구현
위에서 생성한 모듈을 JavaScript (React) 에서 사용하기 위해
패키지 클래스를 애플리케이션 단과 연결해주어야 한다.
패키지는 여러 개의 모듈을 한번에 패키징해서 반환하기 때문에, 앱 당 1개만 있어도 되며
혹은 라이브러리화하여 큰 기능 단위로 하나로 묶어도 된다.
만약 여러개의 모듈을 하나로 패키징하고 싶다면, 해당 모듈들의 공통 부모가 되는 BaseModule 클래스를
상속받게 한 뒤 createNativeModules()의 반환값을 MutableList<BaseModule> 로 반환해주면 된다.
React Native 에서 기본 모듈 목록을 받아오기 위해 createNativeModules() 를 호출하고,
modules.add() 를 통해 reactContext 를 인자로 넘겨주고 모듈 인스턴스를 등록한다.
class NativeBridgePackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new NativeBridgeModule(reactContext));
return modules;
}
}
(4) 애플리케이션 연결
ReactNative 최초 작업 시 앱의 Application 클래스에 ReactApplication 을 상속받은
getReactNativeHost() 에서 반환하는 ReactNativeHost 인스턴스 중 getPackages() 에
위에서 구현한 패키지를 리스트에 추가해 반환하면 된다.
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new NativeBridgePackage());
return packages;
}
iOS 네이티브 모듈
swift 개발 환경인 경우 .swift 클래스에 코드 구현부를 작성한 후 .m 클래스를 통해
Object-C 인 ReactNative 환경에서 사용할 수 있도록 연결해주어야 한다.
(1) 파일 생성
swift 개발 환경에서 진행되므로, 그를 작성한 모듈 파일 (.swift) 을 생성한다.
RN 에서 동일한 코드로 각 OS 를 호출하기 위해서는, 안드로이드에서 구현한 모듈과 같은 클래스명을 사용한다.
또한 생성한 모듈 파일과 Objective-C 을 연동하기 위한 .m 클래스 (구현 클래스) 를 생성해주어야 한다.
그리고 (어떤 역할을 하는 지는 모르겠지만) .h 클래스도 함께 생성한다.
// 파일 예시
NativeBridgeModule.swift
NativeBridge.m
NativeBridge.h
(2) 모듈 클래스 구현
모듈 클래스 (.swift) 는 @objc 어노테이션을 태그하여 사용하고자 하는 클래스명을 명시한다.
그 다음은 구현하고자 하는 코드들을 메서드 단위로 작성하고, 똑같이 @objc 를 사용해 RN 에서 접근할 수 있도록 한다.
이때 첫번째 파라미터 앞에 '_' 를 넣어주어야 한다.
그렇지 않으면 @objc 어노테이션에 파라미터명을 모두 명시해주어야 하는데, .m에서 Objective-C 를 연결할 때
첫번째 파라미터로 함수명을 넘기기 때문에 실제 메서드 파라미터 갯수와 일치하지 않는 오류가 발생한다.
import Foundation
@objc(NativeBridgeModule)
class NativeBridgeModule: NSObject {
@objc
static func requiresMainQueueSetup() -> Bool {
return true
}
@objc(connect:)
func connect(_ str: String) -> Void {
print("success to connect with React Native");
}
}
(3) Objective-C 브릿지 구현
m 클래스를 작성하기 전에 왜인지 모르겠지만 우선 함께 생성한 .h 를 다음과 같이 작성한다.
참고로 Foundation 프레임워크는 데이터 저장, 텍스트 처리, 네트워킹을 포함한 앱 기능과 프레임워크의 기본이 된다고 한다.
// NativeBridge.h
#ifndef NativeBridge_h
#define NativeBridge_h
#import <Foundation/Foundation.h>
@interface NativeBridge : NSObject
@end
#endif /* NativeBridge_h */
이어서 Objective-C 와 연동하기 위해 생성한 .m 클래스를 작성한다.
<React/RCTBridgeModule.h> 헤더파일을 import 받을 뒤 인터페이스를 작성해주어야 한다.
인터페이스를 작성하기 위해서는 RCT_EXTERN_MODULE 을 사용하여 모듈명을 작성하고,
RCT_EXTERN_METHOD 를 사용하여 메서드명을 작성해주면 된다.
// NativeModule.m
#import <React/RCTBridgeModule.h>
#import "NativeBridge.h"
@interface RCT_EXTERN_MODULE(NativeBridgeModule, NSObject)
RCT_EXTERN_METHOD(connect:(NSString *)str);
@end
RN 네이티브 모듈 연결
마지막으로 RN (javascript) 단에서 네이티브 모듈과 연결시켜주면 된다.
간단하게 NativeModules 를 상속받은 뒤, 구현한 모듈의 메서드를 호출해주면 된다.
// NativeBridgeScreen.js
import React from "react";
import { SafeAreaView, View, Text, NativeModules, } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
const { NativeBridgeModule } = NativeModules; // 모듈 추가
function NativeBridgeScreen(props) {
const connectWithNative = async () => {
const result = await NativeBridgeModule.connect("str");
setResultText("네이티브 통신 성공");
};
return (
<SafeAreaView>
<View>
<TouchableOpacity
activeOpacity={0.8}
onPress= { () => { connectWithNative() }}>
<Text>네이티브 통신</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
모듈 연결하는 것과 관련되서는 공식 문서도 잘 정리되어서 무리는 없었다.
다만 스위프트로 개발하는 것이 .. 여전히 익숙하지 않았다는 점 ..
암튼 모듈 설정은 여기까지 ! 👋
© 참고
https://reactnative.dev/docs/native-modules-android (Android Native Modules 공식 문서)
https://reactnative.dev/docs/native-modules-ios (iOS Native Modules 공식 문서)
http://blog.cowkite.com/blog/2004271123/ (Android 와 iOS 에서 RN NativeBridge 구현하기)
'Project > softsphere' 카테고리의 다른 글
[React Native] Access Token 과 Refresh Token (0) | 2021.12.09 |
---|---|
[React Native] 코드 분석하며 마주친 애들 정리 (0) | 2021.10.24 |
[JavaScript] Export Default 에 대해서 (0) | 2021.10.24 |
[React] HTTP API 연동을 위한 Axios vs Fetch API (0) | 2021.10.24 |
[React Native] AsyncStorage (0) | 2021.10.19 |