개발 중이던 Android 앱의 특정 기능들을 다른 ReactNative 앱과 결합할 일이 생겼다. 그래서 가장 먼저 Android 앱에서 필요한 기능들을 하나의 라이브러리 모듈로 통합을 해두었고, ReactNative 앱에서 생성된 Android Project 에 모듈을 Import 해주었다. 해당 모듈에는 필요한 기능들을 모두 사용하는 Activity 까지 넣어두었기 때문에, ReactNative 앱에서 Activity 를 호출해주기만 하면 됐다. 다만, 본 글에서는 ReactNative 에서 Android Project 를 생성하고, Android Project 내에서 ReactNative 에서 사용할 수 있는 함수를 어떻게 만들고, 호출하는지에 대해 정리한다.
1. Create React Native Project
본 글에서는 Expo 를 사용하여 React Native 프로젝트를 생성했다. Expo 는 React Native 앱을 좀 더 쉽고, 빠르게 만들 수 있도록 도와주는 프레임워크라 보면 되는데, 본질은 React Native 로 동일하기 때문에 어떤 프레임워크를 쓰더라도 똑같이 Android Native 모듈을 붙일 수 있다. Expo 나 React Native CLI 를 통해 ReactNative Project 를 생성해주도록 한다. 만약, Expo CLI 를 사용하고 싶다면, 다음 명령어를 통해 Expo CLI 를 설치할 수 있다.
npm install -g expo-cli
ReactNative CLI 나 Expo 를 통해 Project 를 생성하면 에디터로 해당 Project 폴더를 열어준다.
2. Add Button for Android Method
Android 프로젝트를 만들기에 앞서서, Android 앱 내의 메소드를 호출 할 버튼을 만들어준다. React Native 에서 버튼은 "Button" 으로 생성한다. 앞의 "B" 는 무조건 대문자로 와야한다. (소문자로 할 경우 HTML 의 button 으로 인식한다.)
export default function App() {
return (
<View style={styles.container}>
....
<Button
title = "GoToAndroid"
onPress = {() => {
console.log("Clicked");
}}
/>
</View>
);
}
우선, Android 에서 호출할 메소드를 아직 정의하지 않았기 때문에, 버튼을 클릭했을 때 간단한 로그가 뜨도록 한다.
3. Create Android Project from RN
이제 RN Project 에서 Android Project 를 만들어준다. 현재 Expo 에서는 ExpoKit 을 사용하여 Android Project 를 Eject 할 수 있게 해주는데, 곧 Deprecated 된다는 경고문이 붙어있다. (문서 링크) 그래도 지금은 되는 것 같으니, ExpoKit 을 사용해서 Android Project 를 Eject 해준다.
먼저, "app.json" 파일에서 "name", "version", "icon" 을 확인 및 커스텀 해주고, "android" key 의 json 안에는 "package" 와 "versionCode" 를 추가 및 명시해준다. 완료한 모습은 다음과 같다.
{
"expo": {
"name": "Con2And",
"slug": "Con2And",
"version": "1.0.0",
"icon": "./assets/icon.png",
....
"android": {
"package": "com.emotionwave.conand",
"versionCode": 1,
....
}
....
}
}
"name", "package", "version", "versionCode" 는 Android Project 에서 쓰고 싶은 값으로 넣어주면 된다. "icon" 도 원하는 파일로 넣어도 된다. "app.json" 파일 수정이 끝나면, Eject 를 해주도록 한다.
expo eject
or
npx expo prebuild
위 명령어를 사용하면, ios 와 android 폴더가 생성되고 그 안에 각 플랫폼에 맞는 Project 가 생성된다.
이제 생성된 android 폴더를 Android Studio 로 열어서, RN-Android 연동 간에 Android 쪽에서 구현해야될 부분에 대해 먼저 기술한다.
4. ReactContextBaseJavaModule
우선, Android Code 를 ReactNative 에서 인식할 수 있도록 중간에 Wrapper Class 를 만들어줘야한다. 본 섹션의 제목에 있는 "ReactContextBaseJavaModule" 을 상속 받아서, WrapperModule 을 생성한다. 생성 위치는 "[Project_Name]/android/app/src/main/java/[APP_Package]" 이다. 간단히 보면, "MainActivity" 와 "MainApplication" 이 생성되어 있는 위치이다.
// src/main/java/com/emotionwave/conand/ReactWrapperModule.java
package com.emotionwave.conand;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
public class ReactWrapperModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext reactContext;
ReactWrapperModule(ReactApplicationContext context) {
super(context);
this.reactContext = context;
}
@NonNull
@Override
public String getName() {
return "ReactWrapperModule";
}
}
ReactContextBaseJavaModule 을 상속 받으면, getName() 메소드를 오버라이드 해야한다. 해당 메소드는 ReactWrapperModule 클래스의 이름을 String 형으로 반환하도록 한다. React 는 ReactWrapperModule 의 getName() 메소드를 통해, 해당 모듈의 이름을 파악하고 접근한다. getName() 외에 생성자를 따로 넣어준 것을 확인 할 수 있다. ReactNative 와 연동할 Android Java/Kotlin 코드에서도 Context 가 필요한 경우가 있을 것이다. 하지만, Android 에서 사용하던 Context 를 사용하지 않고, ReactApplicationContext 를 사용해줘야한다. 전역 변수로 ReactApplicationContext 값을 설정하여, 다른 메소드에서 사용할 수 있도록 한다.
마지막으로, React 에서 호출할 Android 메소드를 하나 추가한다. 메소드의 내용은 전달 받은 인자값을 Toast 로 띄우는 걸로 한다. React 가 호출할 수 있는 Android 의 메소드를 추가하려면, @ReactMethod 어노테이션을 추가해줘야한다.
// src/main/java/com/emotionwave/conand/ReactWrapperModule.java
public class ReactWrapperModule extends ReactContextBaseJavaModule {
...
@ReactMethod
public void showToast(String message, int number) {
Toast.makeText(reactContext, "MSG: " + message + " / " + number, Toast.LENGTH_SHORT).show();
}
...
}
Toast 에 사용하는 Context 값은 기존에 Android 에서 사용하던 Context 가 아니라 ReactApplicationContext 의 값이 들어가줘야한다. 그리고, showToast() 메소드에서 전달받은 인자값들을 Toast 메시지에 모두 출력하도록 문자열을 구성한다. 이제 React 에서 호출해주는 것만 남았을 것 같지만, 한 단계가 더 남았다.
5. Register the Module (Android Specific)
RN-Android 연동 간에는 Android 에서 만든 WrapperModule 을 등록하는 과정이 필요하다. 처음에 ReactNative 앱이 실행될 때, 앱과 연계된 패키지들을 모두 훑어보게 된다. 그 때, Android Native 로 만든 Package 가 인식되어야, 그와 관련된 메소드들을 실행시킬 수 있다. 해당 등록 과정을 거치지 않으면, React 코드에서 ReactWrapperModule 을 불러오고 싶어도 하루종일 null 만 반환된다.
먼저, WrapperModule 과 React 를 연결해 줄 Package 를 먼저 구현해야한다. ReactPackage 를 implement 하여 클래스를 구성한다. Package 를 생성하는 위치는 위에서 만든 ReactWrapperModule 의 위치와 같다.
// src/main/java/com/emotionwave/conand/ReactWrapperPackage.java
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.List;
public class ReactWrapperPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactApplicationContext) {
return null;
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactApplicationContext) {
return null;
}
}
ReactPackage 를 implements 해주면, 두 메소드를 Override 해야한다. 하나는 NativeModule 들을 생성하는것에 관련된 메소드이고, 다른 하나는 ViewManager 들을 생성하는 것과 관련된 것으로 보인다. ViewManager 는 따로 만들어둔 것이 없으니, createViewManagers() 메소드는 빈 List 가 반환되도록 한다. 반면에 createNativeModules() 메소드는 이미 등록하려고 만들어둔 것이 있으니, 해당 NativeModule 을 넣은 List 가 반환되도록 하면 될 것이다. 구현하면 다음과 같다.
// src/main/java/com/emotionwave/conand/ReactWrapperPackage.java
public class ReactWrapperPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactApplicationContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ReactWrapperModule(reactApplicationContext));
return modules;
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactApplicationContext) {
return Collections.emptyList();
}
}
위에서 언급했듯이, ReactNative 앱이 최초로 실행되어 초기화 과정을 진행하면, 앱과 관련된 Package 들을 순회하여 확인한다. 앱이 Package 목록 확인 과정을 거칠 때, 방금 만든 ReactWrapperPackage 가 확인될 수 있도록 순회하는 앱 관련 Package 목록에 ReactWrapperPackage 를 넣어준다. 이 작업은 새로 클래스를 만드는 것이 아니라, ReactNative 의 Android Project 를 추출할 때 자동으로 만들어진 "MainApplication.java" 파일에서 수정한다. "MainApplication.java" 파일을 보면 "getPackages()" 메소드가 존재한다. 해당 메소드를 아래와 같이 변경해준다.
// src/main/java/com/emotionwave/conand/MainApplication.java
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper(this,
new ReactNativeHost(this) {
....
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Add My Wrapper Package
packages.add(new ReactWrapperPackage());
return packages;
}
....
});
....
}
ReactNativeHost 변수를 초기화하는 코드 속에 오버라이드 되어있는 "getPackages()" 메소드의 내용을 수정하였다. 반환하는 packages 리스트 안에 방금 만든 ReactWrapperPackage() 변수를 넣어서 같이 반환되도록 한 것이다. 이제 React 코드에서 ReactPackage 를 인식하고, ReactPackage 안에 등록했던 ReactWrapperModule 을 인식하게 되어, "showToast()" 메소드를 호출할 수 있다.
6. Call Method from ReactNative
본 글의 위에서 "2. Add Button for Android Method" 파트에서 간단하게 구현했던 Button 으로 돌아간다. 해당 Button 을 클릭했을 때, Android 앱에서 Toast 가 뜨면 본 글의 목표는 성공한 것이다. React 코드에서 Android 의 메소드를 호출하려면, 가장 먼저 Android 에서 만들었던 ReactWrapperModule 을 가져와야할 것이다. 'react-native' 패키지에서 "NativeModules" 라는 객체를 통해 ReactWrapperModule 을 가져올 수 있다.
<Button
title = "GoToAndroid"
onPress = {() => {
const { ReactWrapperModule } = NativeModules;
console.log(ReactWrapperModule);
}}
/>
위 코드에서 주의할 점은, JS 에서 NativeModule 에 대한 변수를 선언할 때, Android 에서 만든 WrapperModule 이름이 JS 에서 선언할 변수명과 같아야한다. 위 코드처럼 Android 에서 ReactWrapperModule 클래스를 만들고, JS 에서도 ReactWrapperModule 이라는 변수로 선언해야, Android 의 WrapperModule 을 가져올 수 있다. 로그를 찍어보면 다음과 같이 나타난다.
로그 안에 "showToast" 라는 문자열이 나오는 것으로 보아, ReactWrapperModule 안에 "showToast()" 메소드가 있음을 인식한 것으로 보인다. 이제 showToast() 메소드를 호출하는 코드는 다음과 같다.
<Button
title = "GoToAndroid"
onPress = {() => {
const { ReactWrapperModule } = NativeModules;
ReactWrapperModule.showToast("Hello Android", 3344);
}}
/>
JS 의 변수로 선언한 ReactWrapperModule 를 통해 showToast() 메소드를 호출했다. showToast() 메소드는 파라미터로 String 과 int 값을 받기 때문에, 두 값을 메소드 호출에 넣어주었다. 그리고, 해당 버튼을 클릭하면 다음과 같이 나타나는 것을 볼 수 있다.
이렇게 ReactNative 와 Android Native 를 결합하여, ReactNative 에서 Android 메소드를 호출해보았다. 다음 글에서는 ReactNativie 프로젝트에서 추출한 Android Project 에서 Activity 를 생성하고, React 로 호출한 메소드로 Activity 이동이 가능한지 살펴본다. 그리고, React Native 에서 Android 를 호출하는 것이 아니라, Android 에서 React Native 로 값을 전달하는 것은 어떻게 진행되는지에 대해 정리한다.
'ReactNative' 카테고리의 다른 글
[React-Native] 3. React Native WebView 띄우기 (2) | 2023.03.26 |
---|---|
[React-Native] 2. 기본 프로젝트 만들기 및 오류 정리 (0) | 2023.03.20 |
[React-Native] Your Ruby version is 2.6.10, but your Gemfile specified 2.7.6 (0) | 2023.03.17 |
[React-Native] INSTALL_FAILED_INSUFFICIENT_STORAGE (1) | 2022.03.29 |
[React-Native] 1. M1 Mac에서 React-Native 환경 구축 (6) | 2022.03.21 |