Project/softsphere

[React Native] Android 와 iOS 네이티브 모듈 구현하기

written by yunwon 2021. 12. 16. 18:55

 

 

현재 프로젝트가 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 구현하기)

 

Android Native Modules · React Native

Welcome to Native Modules for Android. Please start by reading the Native Modules Intro for an intro to what native modules are.

reactnative.dev