본문 바로가기

프론트엔드/React native

RN - Native Component 만들기

반응형

Native Component 만들기

 

이번에 RN 프로젝트에 tmap을 붙이는 작업을 하게되었다. tmap api 공식문서를 확인해보니 js(web), java(android), swift(ios), object C(ios)를 지원하고 있어서

js(web)를 통해서 개발할 경우 웹뷰를 띄워서 사용해야해서 native component를 만들어서 사용할 예정이다.

Android

Android Native UI Components · React Native

공식문서를 참고하여 만들었다.

tmap 사이트에 접속하여 jar 파일을 다운로드 받는다.

공식문서의 가이드를 차례로 따라 하면서 sdk를 설치하고 개발준비를 마친다.

공식문서에는 안나와 있지만

android > app > src > main > AndroidManifest.xml 파일에 아래 코드를 추가 해준다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
		<application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:allowBackup="false"
      android:theme="@style/AppTheme">
    ....
		</application>
<!-- 아래 코드를 추가해줘야 한다. -->
    <queries>
        <package android:name="com.skt.tmap.ku" />
    </queries>
</manifest>

코드를 추가해주지 않으면 tmap sdk를 인식하지 못해서 값이 계속 false로 나올 수도 있다.

android > app > src > main > java > com > 프로젝트 이름 폴더 위치에 java 파일을 생성한다.

view파일 : Activity 화면에 해당하는 파일

manager파일 : view를 컨트롤하는 파일

package파일 : RN에서 사용하려면 패키지로 한 번 감싸줘야 한다.

RNTMapView.java

package com.프로젝트이름;

import android.content.Context;
import androidx.annotation.NonNull;
import com.skt.Tmap.TMapView;

public class RNTMapView extends TMapView {
  private double centerLat=37.566474;//중심좌표 latitude 위도
  private double centerLong=126.985022;//중심좌표 longtitude 경도

  public RNTMapView(@NonNull Context context) {
    super(context);
    this.setSKTMapApiKey("발급받은 api key");
    this.setLanguage(TMapView.LANGUAGE_KOREAN);
    this.setIconVisibility(true);
    this.setZoomLevel(10);
    this.setMapType(TMapView.MAPTYPE_STANDARD);
    this.setCompassMode(true);
    this.setTrackingMode(true);

  }

}

RNTMapViewManager.java

package com.프로젝트이름;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;

public class RNTMapViewManager extends SimpleViewManager<RNTMapView> {

	// react native에서 사용될 이름을 지정한다.
  public static final String REACT_CLASS = "RNTMapView";

  private double paramLat = 37.56716762263041;
  private double paramLng = 127.00049400329632;
  

  public RNTMapViewManager(ReactApplicationContext reactContext) {
    super();
  }

  @Override
  public String getName() {
		// getName()에서 반환하는 이름을 RN에서 사용한다.
    return REACT_CLASS;
  }

  @Override
  public RNTMapView createViewInstance(ThemedReactContext reactContext) {
    RNTMapView tMapView = new RNTMapView(reactContext);
    return tMapView;
  }
}

RNTMapViewPackage.java

package com.프로젝트이름;

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.List;

public class TMapPackage implements ReactPackage {

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        List<ViewManager> managers = new ArrayList<>();
        managers.add(new RNTMapViewManager(reactContext));

        return managers;
    }

    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new CustomModule(reactContext));

        return modules;
    }
}

위 3개의 파일을 만들고 yarn android 명령어로 android build를 해준다.

js 파일에서 사용하기

RNTMapView.tsx

import { HostComponent, ViewStyle, requireNativeComponent } from "react-native";

interface IRNTMapView {
    style: ViewStyle;
}

export const RNTMapView: HostComponent<IRNTMapView> = requireNativeComponent("RNTMapView");
// getName()에서 반환한 이름과 같은 이름을 사용한다.

main.tsx

const MainScreen: React.FC<NativeStackScreenProps<RootModalStackParamList>> = ({
    navigation
}) => {
    return (
        <View style={{ flex: 1 }}>
	        <RNTMapView style={{ flex: 1 }} />
        </View>
    );
};

export default MainScreen;

위와 같이 만들어 사용했더니 문제가 생겼다!!

React Native에서 0.68 버전부터 Bridge 방식이 아닌 새로운 아키텍처로 Fabric을 사용하게 되었다.

그레서 위 방식으로 만들경우 Fabric 옵션을 사용하면 나오지 않는다.

그래서 Fabric Native Components · React Native 공식문서를 참고하여 React Native의 새로운 아키텍처인 Fabric을 사용한 Native Component를 생성 해봤다.

 

Fabric

1. 폴더 만들기

프로젝트 root 경로에 native component 폴더를 만든다.

# root 경로에서
mkdir RNTMapView

RNTMapView 폴더 내부에는

  1. android
  2. ios
  3. js

위 3개의 폴더를 생성한다.

2. js 파일 만들기

js 폴더 내부에 <MODULE_NAME>NativeComponent.(js,jsx,ts,tsx) 파일을 만든다.

RNTMapViewNativeComponent.ts

import type { ViewProps } from "ViewPropTypes";
import type { HostComponent } from "react-native";
import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent";

export interface NativeProps extends ViewProps {
    // add other props here
}

export default codegenNativeComponent<NativeProps>("RNTMapView") as HostComponent<NativeProps>;

3. package.json 파일 생성

{
    "name": "rn-tmap-view",
    "version": "0.0.1",
    "description": "Fabric Native Component with a TMap View",
    "react-native": "js/index",
    "source": "js/index",
    "files": [
        "js",
        "android",
        "ios",
        "rtn-centered-text.podspec",
        "!android/build",
        "!ios/build",
        "!**/__tests__",
        "!**/__fixtures__",
        "!**/__mocks__"
    ],
    "keywords": [
        "react-native",
        "ios",
        "android"
    ],
    "repository": "<https://github.com/>/rtn-centered-text",
	  "author": " <your_email@your_provider.com> (<https://github.com/>)",
	  "license": "MIT",
	  "bugs": {
	    "url": "<https://github.com/>/rtn-centered-text/issues"
	  },
	  "homepage": "<https://github.com/>/rtn-centered-text#readme",
    "devDependencies": {},
    "peerDependencies": {
        "react": "*",
        "react-native": "*"
    },
    "codegenConfig": {
        "name": "RNTMapViewSpecs",
        "type": "components",
        "jsSrcsDir": "js"
    }
}
</your_email@your_provider.com>

4. ios .podspec 파일 생성

require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

Pod::Spec.new do |s|
  s.name            = "rn-tmap-view"
  s.version         = package["version"]
  s.summary         = package["description"]
  s.description     = package["description"]
  s.homepage        = package["homepage"]
  s.license         = package["license"]
  s.platforms       = { :ios => "12.0" }
  s.author          = package["author"]
  s.source          = { :git => package["repository"], :tag => "#{s.version}" }

  s.source_files    = "ios/lib/Headers/*.{h,m,mm,swift}"
  s.source_files  = "ios/*.{h,m}"

  
  s.resources = ['ios/lib/Images/*.png']

  install_modules_dependencies(s)
end

5. build.gradle

android > build.gradle

buildscript {
  ext.safeExtGet = {prop, fallback ->
    rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
  }
  repositories {
    google()
    gradlePluginPortal()
  }
  dependencies {
    classpath("com.android.tools.build:gradle:7.3.1")
  }
}

apply plugin: 'com.android.library'
apply plugin: 'com.facebook.react'

android {
  compileSdkVersion safeExtGet('compileSdkVersion', 33)
  namespace "com.rntmapview"

  defaultConfig {
    minSdkVersion safeExtGet('minSdkVersion', 21)
    targetSdkVersion safeExtGet('targetSdkVersion', 33)
    buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true")
  }
}

repositories {
  mavenCentral()
  google()
}

dependencies {
  implementation 'com.facebook.react:react-native'
  implementation files('libs/com.skt.Tmap_1.75.jar')
}

6. java file

android > src > main > java > com > rntmapview

RNTMapView.java

package com.rntmapview;

import android.content.Context;
import androidx.annotation.NonNull;
import com.skt.Tmap.TMapView;

public class RNTMapView extends TMapView {
  private double centerLat=37.566474;//중심좌표 latitude 위도
  private double centerLong=126.985022;//중심좌표 longtitude 경도

  public RNTMapView(Context context) {
    super(context);
    this.configureComponent();

  }

  private void configureComponent() {
    this.setSKTMapApiKey("발급받은 api key");
    this.setLanguage(TMapView.LANGUAGE_KOREAN);
    this.setIconVisibility(true);
    this.setZoomLevel(10);
    this.setMapType(TMapView.MAPTYPE_STANDARD);
    this.setCompassMode(true);
    this.setTrackingMode(true);
  }

}

RNTMapViewManager.java

package com.rntmapview;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.viewmanagers.RNTMapViewManagerInterface;
import com.facebook.react.viewmanagers.RNTMapViewManagerDelegate;
import com.skt.Tmap.TMapView;

@ReactModule(name = RNTMapViewManager.REACT_CLASS)
public class RNTMapViewManager extends SimpleViewManager<RNTMapView> implements RNTMapViewManagerInterface<RNTMapView> {
    private final ViewManagerDelegate<RNTMapView> mDelegate;
    public static final String REACT_CLASS = "RNTMapView";
    ReactApplicationContext mCallerContext;

    public RNTMapViewManager(ReactApplicationContext reactContext) {
        mCallerContext = reactContext;
        mDelegate = new RNTMapViewManagerDelegate<>(this);
    }

    @Nullable
    @Override
    protected ViewManagerDelegate<RNTMapView> getDelegate() {
        return mDelegate;
    }

    @Override
    public String getName() {
        return REACT_CLASS;
    }

    @NonNull
    @Override
    protected RNTMapView createViewInstance(@NonNull ThemedReactContext context) {
        return new RNTMapView(context);
    }

    // props를 추가할 때마다 아래처럼 추가해준다.
    // @Override
    // @ReactProp(name = "text")
    // public void setText(RNTMapView view, @Nullable String text) {
        
    // }

}

RNTMapViewPackage.java

package com.rntmapview;

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.Collections;
import java.util.List;

public class RNTMapViewPackage implements ReactPackage {

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.singletonList(new RNTMapViewManager(reactContext));
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

}

7. ios

RNTMapViewManager.mm

RNTMapView.mm

RNTMapView.h

파일 생성

8. 설치

code generate

# root 경로에서
yarn add ./RNTMapView
# android
cd android
./gradlew generateCodegenArtifactsFromSchema # code generate 하는 코드 
# ios
node MyApp/node_modules/react-native/scripts/generate-codegen-artifacts.js \\
  --path MyApp/ \\
  --outputPath RTNCenteredText/generated/

설치

# ios
cd ios
RCT_NEW_ARCH_ENABLED=1 bundle exec pod install
# android
yarn android

ios

ios

Codegen으로 스캐폴딩 코드를 생성할 때, iOS에서는 빌드 폴더를 자동으로 정리하지 않습니다. 예를 들어, Spec 이름을 변경하고 Codegen을 다시 실행하면 이전 파일이 유지됩니다. 그렇다면 Codegen을 다시 실행하기 전에 build 폴더를 제거해야 합니다.

ios 폴더로 이동해서 아래 명령어를 실행한다.

cd MyApp/ios # ios로 이동
rm -rf build # build 폴더 정리

⇒ ios는 swift \ object-C 문법 공부가 부족해 실패했다..

 

 

 

 

참고자료

Fabric Native Components · React Native

 

Fabric Native Components · React Native

A Fabric Native Component is a Native Component rendered on the screen using the Fabric Renderer. Using Fabric Native Components instead of Legacy Native Components allows us to reap all the benefits of the New Architecture:

reactnative.dev

https://github.com/react-native-community/RNNewArchitectureLibraries/blob/feat/back-fabric-component-070/colored-view/ios/RNColoredViewManager.mm

https://github.com/react-native-community/RNNewArchitectureLibraries/blob/feat/component-with-state/image-component/ios/RTNImageComponentView.mm

반응형