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 폴더 내부에는
- android
- ios
- 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
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