๊ฐœ๋ฐœ/Zooda

React Native + Kotlin ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ ์•ฑ ๊ฐœ๋ฐœ (Metro ์„œ๋ฒ„ ํฌํ•จ ์ „์ฒด ๊ตฌ์กฐ ์„ค๋ช…)(chatgpt)

da 2025. 6. 29. 11:02

๐Ÿš€ React Native + Kotlin ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ ์•ฑ ๊ฐœ๋ฐœ (Metro ์„œ๋ฒ„ ํฌํ•จ ์ „์ฒด ๊ตฌ์กฐ ์„ค๋ช…)

React Native๋Š” ๋น ๋ฅธ UI ๊ฐœ๋ฐœ์— ์ตœ์ ํ™”๋œ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ธ”๋ฃจํˆฌ์Šค, ์„ผ์„œ, GPS ๋“ฑ Android ๊ณ ์œ  ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Kotlin ๋˜๋Š” Java๋กœ ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” React Native + Kotlin ์—ฐ๋™ ์˜ˆ์ œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ, ์•ฑ์ด ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑ๋˜๊ณ , ์–ด๋–ป๊ฒŒ ์‹คํ–‰๋˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  Metro ์„œ๋ฒ„์˜ ์—ญํ• ๊นŒ์ง€ ์ „์ฒด ํ๋ฆ„์„ ์ •๋ฆฌํ•ด๋ด…๋‹ˆ๋‹ค.


๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ์š”์•ฝ

test_project/
โ”œโ”€โ”€ App.js
โ”œโ”€โ”€ /screens
โ”‚   โ”œโ”€โ”€ HomeScreen.js
โ”‚   โ””โ”€โ”€ SecondScreen.js
โ”œโ”€โ”€ /android/app/src/main/java/com/test_project
โ”‚   โ”œโ”€โ”€ MyNativeModule.kt       โ† ๋„ค์ดํ‹ฐ๋ธŒ ๊ธฐ๋Šฅ ์ •์˜
โ”‚   โ””โ”€โ”€ MyPackage.kt            โ† ํŒจํ‚ค์ง€ ๋“ฑ๋ก
โ””โ”€โ”€ /android/app/src/main/java/com/test_project/MainApplication.kt

โš™๏ธ ๊ตฌ์„ฑ์š”์†Œ ์„ค๋ช… (Metro ํฌํ•จ)

๊ตฌ์„ฑ ์š”์†Œ์–ธ์–ด์—ญํ• 
App.jsJS์•ฑ ์ง„์ž…์ , ํ™”๋ฉด ์ „ํ™˜ ์ •์˜
HomeScreen.jsJS์ฒซ ํ™”๋ฉด, ๋ฒ„ํŠผ ํด๋ฆญ์œผ๋กœ ํ™”๋ฉด ์ „ํ™˜
SecondScreen.jsJSKotlin ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ ํ˜ธ์ถœ ๋ฐ ๊ฒฐ๊ณผ ํ‘œ์‹œ
MyNativeModule.ktKotlinJS์—์„œ ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•œ ๋„ค์ดํ‹ฐ๋ธŒ ํ•จ์ˆ˜ ์ •์˜
MyPackage.ktKotlinKotlin ๋ชจ๋“ˆ์„ RN์— ๋“ฑ๋ก
MainApplication.ktKotlin์•ฑ ์ดˆ๊ธฐํ™” ๋ฐ ํŒจํ‚ค์ง€ ์ˆ˜๋™ ๋“ฑ๋ก
Metro ServerNode.js (CLI)JS ๋ฒˆ๋“ค ์ƒ์„ฑ ๋ฐ ์•ฑ์— ์ „์†ก (๊ฐœ๋ฐœ ์‹œ ํ•„์ˆ˜)

๐Ÿ“„ ์ฃผ์š” ํŒŒ์ผ ์ฝ”๋“œ ์˜ˆ์‹œ

1. App.js

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';
import SecondScreen from './screens/SecondScreen';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Second" component={SecondScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

2. HomeScreen.js

import React from 'react';
import { View, Button, StyleSheet } from 'react-native';

const HomeScreen = ({ navigation }) => {
  return (
    <View style={styles.container}>
      <Button
        title="๋„ค์ดํ‹ฐ๋ธŒ ๋ฉ”์‹œ์ง€ ๋ณด๊ธฐ"
        onPress={() => navigation.navigate('Second')}
      />
    </View>
  );
};

export default HomeScreen;

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});

3. SecondScreen.js

import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, NativeModules } from 'react-native';

const { MyNativeModule } = NativeModules;

const SecondScreen = () => {
  const [message, setMessage] = useState('...');

  useEffect(() => {
    MyNativeModule.getNativeMessage()
      .then(result => setMessage(result))
      .catch(error => setMessage(`Error: ${error.message}`));
  }, []);

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Kotlin์—์„œ ์˜จ ๋ฉ”์‹œ์ง€:</Text>
      <Text style={styles.message}>{message}</Text>
    </View>
  );
};

export default SecondScreen;

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  title: { fontSize: 20, marginBottom: 10 },
  message: { fontSize: 24, color: 'blue' },
});

4. MyNativeModule.kt

package com.test_project

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod

class MyNativeModule(reactContext: ReactApplicationContext) :
    ReactContextBaseJavaModule(reactContext) {

    override fun getName(): String {
        return "MyNativeModule"
    }

    @ReactMethod
    fun getNativeMessage(promise: Promise) {
        promise.resolve("์•ˆ๋…•ํ•˜์„ธ์š”! Kotlin์—์„œ ์™”์–ด์š” ๐ŸŽ‰")
    }
}

5. MyPackage.kt

package com.test_project

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class MyPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return listOf(MyNativeModule(reactContext))
    }

    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList()
    }
}

6. MainApplication.kt

import com.test_project.MyPackage // ์ƒ๋‹จ import ์ถ”๊ฐ€

override fun getPackages(): List<ReactPackage> =
    PackageList(this).packages.apply {
        add(MyPackage()) // ๐Ÿ‘ˆ ๋„ค์ดํ‹ฐ๋ธŒ ํŒจํ‚ค์ง€ ์ˆ˜๋™ ๋“ฑ๋ก
    }

โš™๏ธ ๋นŒ๋“œ ๋ฐ ์‹คํ–‰ ๊ณผ์ •

  1. Metro ์„œ๋ฒ„ ์‹คํ–‰ - JS ์ฝ”๋“œ ๋ฒˆ๋“ค ์ƒ์„ฑ ์„œ๋ฒ„
    npx react-native start --reset-cache
  2. ์•ฑ ๋นŒ๋“œ ๋ฐ ์„ค์น˜ - Kotlin ํฌํ•จ APK ๋นŒ๋“œ
    npm run android
  3. ADB๋ฅผ ํ†ตํ•ด APK๊ฐ€ ๋””๋ฐ”์ด์Šค์— ์„ค์น˜ ๋ฐ ์‹คํ–‰
  4. ์•ฑ ์‹คํ–‰ โ†’ JS Bundle ๋กœ๋”ฉ (http://10.0.2.2:8081/...)

โ–ถ๏ธ ์•ฑ ๊ตฌ๋™ ์ˆœ์„œ (์ „์ฒด ํ๋ฆ„)

1. ์‚ฌ์šฉ์ž ์•ฑ ์‹คํ–‰
2. Android โ†’ MainApplication.kt โ†’ ReactNativeHost ์ดˆ๊ธฐํ™”
3. Metro ์„œ๋ฒ„์—์„œ JS ๋ฒˆ๋“ค ๋‹ค์šด๋กœ๋“œ
4. React Native ๋Ÿฐํƒ€์ž„ ์‹คํ–‰ โ†’ App.js โ†’ HomeScreen
5. ๋ฒ„ํŠผ ํด๋ฆญ โ†’ SecondScreen
6. NativeModules.MyNativeModule.getNativeMessage() ํ˜ธ์ถœ
7. Kotlin ์ฝ”๋“œ ์‹คํ–‰ โ†’ ๋ฉ”์‹œ์ง€ ๋ฐ˜ํ™˜
8. React Native ํ™”๋ฉด์— ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ

๐Ÿ“Œ ์ •๋ฆฌ

  • React Native๋Š” UI ๋ฐ ํ™”๋ฉด ์ „ํ™˜ ๋‹ด๋‹น
  • Kotlin์€ ํ•˜๋“œ์›จ์–ด ๊ธฐ๋Šฅ, ์‹œ์Šคํ…œ ์ ‘๊ทผ ๋‹ด๋‹น
  • Metro ์„œ๋ฒ„๋Š” ๊ฐœ๋ฐœ ์‹œ ํ•„์ˆ˜์ ์ธ JS ์ฝ”๋“œ ๊ณต๊ธ‰ ์„œ๋ฒ„
  • ๋‘˜ ์‚ฌ์ด๋ฅผ NativeModules๋กœ ์—ฐ๊ฒฐ