# React Native

<figure><img src="https://169964493-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwMb5WkedgvZ9Yk3Yxqep%2Fuploads%2FoCyaDTPB6oJRvlgbwXqa%2FFlutter.png?alt=media&#x26;token=d20e6582-bf40-40cc-96a5-06c601be77b4" 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="https://169964493-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwMb5WkedgvZ9Yk3Yxqep%2Fuploads%2FOYitRVCGLNWszlDzBgNL%2Fimage.png?alt=media&#x26;token=3317e6ad-98b3-400d-ae82-754bb9146e3d" 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 %}
