> For the complete documentation index, see [llms.txt](https://bleepy.gitbook.io/bleepy-developers/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bleepy.gitbook.io/bleepy-developers/game-promotion/react-native.md).

# React Native

<figure><img src="/files/U6sDn6w4dwsmJjHU9976" alt=""><figcaption></figcaption></figure>

***

## 1단계: react-navigation 라이브러리 설치

스크린 이동을 위해 <mark style="color:blue;">`react-navigation`</mark> 패키지를 설치합니다.

```sh
npm install @react-navigation/native
```

해당 가이드는 <mark style="color:blue;">`Native Stack Navigator`</mark>를 사용합니다.

```sh
npm install @react-navigation/native-stack
```

위 라이브러리와 의존성(dependencies)을 가지는 추가 라이브러리 설치가 필요합니다.

```sh
npm install react-native-screens react-native-safe-area-context
```

iOS의 경우 [<mark style="color:orange;">\[Cocoapods\]</mark>](https://cocoapods.org/) 를 통해 <mark style="color:blue;">`Pod`</mark> 설치가 필요합니다.

```sh
npx pod-install ios
```

***

## 2단계: 런처 스크린 화면 구성

자사 서비스 내 인앱 게임을 구동하기 위해서는 <mark style="color:blue;">`WebView`</mark> 를 포함하는 스크린의 생성이 필요합니다.\
다음 가이드를 따라 진행 해주시기 바랍니다.

### 1. 런처 스크린 파일 생성

<mark style="color:blue;">`src/screens`</mark> 경로에 런처용 스크린 화면을 추가합니다. \
예시로 <mark style="color:blue;">`src/screens/launcher/Launcher.tsx`</mark> 로 작성하였습니다.

### 2. Navigation 라우팅 추가

<mark style="color:blue;">`App.tsx`</mark> 파일에서 <mark style="color:blue;">`Navigator`</mark> 관련 정의를 추가합니다.\ <mark style="color:blue;">`Stack Navigator`</mark>에 하위로 사용할 스크린에 해당하는 <mark style="color:blue;">`Stack`</mark>을 생성합니다. \ <mark style="color:blue;">`Stack.Screen`</mark> 의 <mark style="color:blue;">`name`</mark> 값이 라우팅 경로가 되고, <mark style="color:blue;">`component`</mark> 는 스크린 컴포넌트를 넣어주세요.

예시 가이드에서는 <mark style="color:blue;">`launcher`</mark> , <mark style="color:blue;">`banner`</mark> 2가지 스크린을 사용하였습니다.

```typescript
// App.tsx
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import HomeScreen from '@screens/home';
import LauncherScreen from '@screens/launcher';

const Stack = createNativeStackNavigator();

export type RootStackParam = {
  launcher: undefined;
  banner: {url: string};
};

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator screenOptions={{headerShown: false}}>
        <Stack.Screen name="launcher" component={LauncherScreen} />
        <Stack.Screen name="banner" component={BannerScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
```

### 3. WebView에 런처 호출 URL 로드

<mark style="color:blue;">`WebView`</mark> 를 사용하기 위해서 <mark style="color:blue;">`react-native-webview`</mark> NPM 라이브러리를 설치 합니다.

```sh
npm i react-native-webview
```

런처 스크린 내에 <mark style="color:blue;">`WebView`</mark> 컴포넌트를 추가하고 <mark style="color:blue;">`source`</mark> 속성에 <mark style="color:blue;">`런처 호출 URL`</mark>을 요청합니다.

* 자바스크립트 활성화를 위해 <mark style="color:blue;">`javaScriptEnabled`</mark> 값을 <mark style="color:blue;">`true`</mark> 로 설정합니다.
* iOS는 메모리 문제로 <mark style="color:blue;">`Terminate`</mark> 발생 가능성이 있어 <mark style="color:blue;">`reload`</mark> 처리를 추가적으로 권장합니다.
  * <mark style="color:blue;">`WebView`</mark> 관련 <mark style="color:blue;">`ref`</mark> 선언을 추가합니다.
  * <mark style="color:blue;">`WebView`</mark> 컴포넌트 내 <mark style="color:blue;">`onContentProcessDidTerminate`</mark> 콜백을 통해 <mark style="color:blue;">`reload`</mark> 처리합니다.

```typescript
// src/screens/launcher/Launcher.tsx

import { WebView } from 'react-native-webview';
import { useRef } from 'react';

export default function Launcher() {
  const webviewRef = useRef<any>();

  ...
  return (
    ...
    <WebView
      ref={webviewRef}
      source={{
        uri: {런처 호출 URL},
        headers: {'Cache-Control': 'no-cache'},
      }}
      onMessage={onMessage}
      onContentProcessDidTerminate={() => {
        // webview reload
        webviewRef.current?.reload();
      }}
      javaScriptEnabled={true}
    />
    ...
  )
}
```

***

## 3단계: onMessage 설정 추가

런처와 React Native간의 통신을 수행하기 위해 <mark style="color:blue;">`WebView`</mark> 의 <mark style="color:blue;">`onMessage`</mark> 콜백을 정의합니다. \ <mark style="color:blue;">`WebView`</mark> 에 로드된 런처에서 React Native로 메세지 이벤트를 전달합니다.\
다음은 런처에서 전달하는 이벤트 케이스 입니다.

<table><thead><tr><th width="219">Type</th><th>Description</th></tr></thead><tbody><tr><td><mark style="color:orange;"><code>closeLauncher</code></mark></td><td>런처의 Back 버튼 UI 클릭 시 (뒤로가기 처리)</td></tr><tr><td><mark style="color:orange;"><code>launcherLoaded</code></mark></td><td>런처 로드가 완료된 시점에 호출</td></tr><tr><td><mark style="color:orange;"><code>timerMissionComplete</code></mark></td><td>타이머 미션이 완료 되었을 때 호출</td></tr><tr><td><mark style="color:orange;"><code>giftReceived</code></mark></td><td>육성완료 시 호출</td></tr></tbody></table>

```typescript
// src/screens/launcher/Launcher.tsx

import {WebView, WebViewMessageEvent} from 'react-native-webview';

export default function Launcher() {
  // WebView에서 보내온 메세지 처리
  const onMessage = (e: WebViewMessageEvent) => {
    const {type, inviteCode, infoMessage, link}
      = JSON.parse(e.nativeEvent.data);

    switch (type) {
      case 'closeLauncher':
        closeLauncher();
        break;
      case 'launcherLoaded':
        launcherLoaded();
        break;
      case 'timerMissionComplete':
        timerMissionComplete();
        break;
      case 'giftReceived':
        giftReceived();
        break;
    }
  };

  return (
    <View>
      <WebView
        ...
        onMessage={onMessage}
        ...
      />
    </View>
  );
}
```

```typescript
// src/screens/launcher/Launcher.tsx

// 런처 종료 - 블리피 런처의 Back 버튼 UI 클릭 시 React Native에서는 뒤로가기 처리를 수행합니다.
const closeLauncher = () => {
  if (navigation.canGoBack()) {
    navigation.goBack();
  }
};

// 런처 로드 완료
const launcherLoaded = () => {
  // 런처의 로드가 완료된 후 추가적인 처리 필요 시 사용
};

// 타이머 미션 완료
const timerMissionComplete = () => {
  // 타이머 미션 완료 후 추가적인 처리 필요 시 사용
};

// 육성완료
const giftReceived = () => {
  // 육성완료 후 추가적인 처리 필요 시 사용
};
```

***

## 4단계: 런처 스크린 세로모드 고정

게임은 세로모드 해상도에 최적화 되어 스크린의 세로모드 고정이 필요합니다.\
세로모드 고정을 위해 <mark style="color:blue;">`react-native-orientation-locker`</mark> 라이브러리를 설치합니다.

```sh
npm install --save react-native-orientation-locker
```

* 세로모드 고정 코드를 추가합니다.

```typescript
// 추가
import Orientation from 'react-native-orientation-locker';

export default function Launcher() {
  ...
  
  // 추가
  useEffect(() => {
    // 화면 방향을 세로로 고정합니다.
    Orientation.lockToPortrait();

    return () => {
      // clean-up에서 다른 스크린에 영향 없도록 Orientations 해제
      Orientation.unlockAllOrientations();
    };
  }, []);

  return (
    <View>
      <WebView ... />
    </View>
  );
}
```

* iOS 환경에서는 추가 설정이 필요합니다.
  * <mark style="color:blue;">`/ios/AppDelegate.m`</mark> 파일을 열고 아래 코드를 추가합니다.
  * <mark style="color:blue;">`pod install`</mark> 실행합니다.

```objectivec
// AppDelegate.m or AppDelegate.mm

// 추가
#import "Orientation.h"

@implementation AppDelegate
...

// 추가
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
  return [Orientation getOrientation];
}

...
@end
```

***

## 5단계: 화면 전환 설정

### 1. 딥링킹을 위한 설정 추가

React Native 환경 내 <mark style="color:blue;">`Android`</mark> / <mark style="color:blue;">`iOS`</mark> 두 플랫폼에서 딥링킹 처리를 위한 가이드입니다.

#### <mark style="color:orange;">`Android`</mark>

* 프로젝트 루트 경로 내 <mark style="color:blue;">`android/app/src/main/AndroidManifest.xml`</mark> 파일 내 딥링킹 처리를 위한 <mark style="color:blue;">`intent-filter`</mark> 를 추가합니다.
* <mark style="color:blue;">`scheme`</mark> 값은 프로모션 설정 시 입력할 딥링크에 사용하실 스키마 값으로 세팅해주세요.

```xml
// android/app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET" />

    <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">
      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>

        <!-- 딥링크 이동을 위한 intent-filter 추가 -->
        <intent-filter>
          <action android:name="android.intent.action.VIEW" />
          <category android:name="android.intent.category.DEFAULT" />
          <category android:name="android.intent.category.BROWSABLE" />
          <data
            android:scheme="{사용할 scheme}" />
        </intent-filter>

      </activity>
    </application>
</manifest>
```

***

#### <mark style="color:orange;">`iOS`</mark>

<mark style="color:blue;">`scheme`</mark> 관련 정보는 <mark style="color:blue;">`직접 코드 추가`</mark> 또는 <mark style="color:blue;">`Xcode 내 설정`</mark>하는 2가지 방법이 있습니다.

* 직접 코드 추가
  * <mark style="color:blue;">`info.plist`</mark> 파일에 직접 코드를 추가합니다.
  * <mark style="color:blue;">`CFBundleURLTypes`</mark> 관련 내용을 아래와 같이 추가합니다.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    ...
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeRole</key>
            <string>Editor</string>
            <key>CFBundleURLName</key>
            <string>{식별자(scheme와 동일하게 세팅해도 무관)}</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>{사용할 scheme}</string>
            </array>
	</dict>
    </array>
    ...
</dict>
</plist>
```

* Xcode 내 설정
  * URL Types에 <mark style="color:blue;">`scheme`</mark> 추가합니다.

<figure><img src="/files/nLlGNJNBjKjssSVNzLj6" alt=""><figcaption></figcaption></figure>

프로젝트 <mark style="color:blue;">`/ios`</mark> 경로 내에 몇가지 설정을 더 추가합니다.

```objectivec
// ios/프로젝트명/AppDelegate.mm

// 파일 상단에 추가
#import <React/RCTLinkingManager.h>

...

// @end위에 추가
- (BOOL)application:(UIApplication *)application
   openURL:(NSURL *)url
   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
  return [RCTLinkingManager application:application openURL:url options:options];
}

...
@end
```

***

### 2. NavigationContainer linking 추가

React Native에서는 <mark style="color:blue;">`NavigationContainer`</mark> 의 <mark style="color:blue;">`linking`</mark> 기능을 사용하여 딥링크를 컨트롤합니다.

```typescript
// App.tsx
export default function App() {
  // 사용할 scheme, host를 통해 이동할 screen을 지정
  const linking = {
    prefixes: ['{scheme}://'],
    screens: {
      {host}: 'screenName',
    },
  };

  return (
    // linking set
    <NavigationContainer linking={linking}>
      <Stack.Navigator screenOptions={{headerShown: false}}>
        ...
      </Stack.Navigator>
    </NavigationContainer>
  );
}
```

{% hint style="info" %}
개발에 대한 추가 설명이 더 필요하신가요?

"[<mark style="color:orange;">\[Client Admin\]</mark>](https://client-admin.bleepy.io/login) 로그인 → 오른쪽 하단 채널톡 위젯" 클릭 후 개발 카테고리에 문의 남겨주시면 기술 개발팀에서 확인 후 연락드리겠습니다.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://bleepy.gitbook.io/bleepy-developers/game-promotion/react-native.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
