어서와, 개발은 처음이지?

6.React Native Navigation(v1) 기초 - 1부 설치하기 본문

React/React Native

6.React Native Navigation(v1) 기초 - 1부 설치하기

오지고지리고알파고포켓몬고 2018.08.02 18:52


이 글은

6.React Native Navigation 기초 - 1부 설치하기(현재글)

6.React Native Navigation 기초 - 2부 화면 등록, 화면 이동(startapp, push, pop)

6.React Native Navigation 기초 - 3부 기능 살펴보기로 구성되어 있습니다.



우리가 사용하는 일반적인 앱들은 버튼을 누르면 새로운 화면으로 이동하고, 뒤로가기 버튼(혹은 제스처)을 누르면 다시 이전화면으로 이동하는 모습을 볼 수 있습니다.


지난 글에서 3가지 화면(시작 화면, 회원가입 화면, 로그인 화면)을 구현했는데, 그렇다면 리액트 네이티브에서 화면 간 이동이 가능하도록 구현 하려면 어떻게 해야할까요?


바로 Navigation을 사용하여 손쉽게 구현할 수 있습니다.


네비게이션은 원하는 화면으로의 이동, 지나온 화면들을 기억하는 역할을 합니다. 자동차에 달린 그것의 기능과 흡사하다고 할 수 있겠네요.


이 네비게이션은 리액트 네이티브에서 제공하는 네비게이션 이외에도 React Navigation, React Native Router Flux, React Native Navigation 등 다양한 외부 라이브러리가 존재합니다.

그 중에서 React Native Navigation을 사용하겠습니다.


React Native Navigation은 다른 라이브러리에 비교했을 때 View로 네비게이션을 흉내낸 것이 아니고 네이티브 네비게이션을 사용하기 때문에 속도가 더 빠르다고 합니다.

또, 나름 이름있는 wix에서 만든 라이브러리라 지속적으로 관리가 이루어진다는 장점이 있습니다.


다만 저희가 사용할 버전 1.1에서는 custom navbar를 사용할 경우 가끔 버그가 발생하는 상황이 있는데, 이는 버전 2에서 수정되었다고 합니다.

아직 버전 2는 stable하지 않으니 궁금하시다면 직접 사용해보시는 것도 좋을 것 같습니다.


그럼 이번 주제에서는 React Native Navigation을 설치하고 화면 간 이동이 가능하도록 구현해보겠습니다.



1.React Native 다운그레이드


React Native Navigation 1.1.xxx은 아직 React Native 0.56 버전을 지원하지 않는것 같습니다.

때문에 package.json에서 React Native을 0.55.4로 babel-preset-react-native를 4.0.0으로 버전을 낮추는 작업을 먼저 진행합니다.

{
  "name": "TestProject",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest"
  },
  "dependencies": {
    "react": "16.4.1",
    "react-native": "0.55.4"
  },
  "devDependencies": {
    "babel-jest": "23.2.0",
    "babel-preset-react-native": "^4.0.0",
    "jest": "23.3.0",
    "react-test-renderer": "16.4.1"
  },
  "jest": {
    "preset": "react-native"
  }
}

package.json은 위와 같이 수정해주시고 프로젝트 경로로 이동해서 npm install 명령을 입력합니다.

2.React Native Navigation 설치하기


React Native Navigation에 관한 공식 문서는 이 곳에서 확인하실 수 있습니다.

우선 /TestProject로 이동해서 npm으로 react native navigation을 설치하도록 합니다.

프로젝트는 새로 만드셔도 상관없습니다.


첩보에 의하면 1.1.473버전 이후에 버그가 있다고하니 1.1.472로 설치하도록 하겠습니다.

$ cd TestProject
$ npm install react-native-navigation@1.1.472 --save

잠시 시간이 흐른 뒤에 아래와 같이 설치가 완료됐다는 메세지를 볼 수 있습니다.

3.안드로이드 세팅


외부 모듈을 사용할 때, 종종 각 네이티브 프로젝트의 설정 파일에 별도의 세팅이 필요합니다.

이를 편하게 하기위해 rnpm link, react-native link 명령을 제공하는 모듈도 있지만, react 버전이나 기 참조중인 모듈의 구조에 영향을 받을 수 있으므로 개인적으로는 수동 세팅을 권장합니다.


일전에 어떤 모듈을 설치하다가 설정파일이 그 모듈만 의존하는 식으로 덮혀버렸는데 지금 생각해도 아찔하네요.


다시 돌아와서, 별도의 세팅이 필요한 모듈들은 웬만하면 npm 또는 git repo의 read me에  설정 방법이 명시 되어있습니다.

우리는 React Native Navigation의 공식 문서에 의거하여 세팅 방법을 짚어보겠습니다.


첫번째로 settings.gradle을 수정해야합니다.(npm을 통해 react native navigation을 설치했으니 yarn add ... 부분은 넘어갑니다.)

안드로이드의 네이티브와 관련된 소스는 /TestProject/android에 위치하고 있습니다.

 위 경로로 이동해보면 settings.gradle이라는 파일을 볼 수 있는데, 이 파일을 열어서 하단 부분에 아래 코드를 추가합니다.

include ':react-native-navigation'
project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-navigation/android/app/')

수정된 settings.gradle의 전체 내용은 아래와 같습니다.

rootProject.name = 'TestProject'

include ':app'

include ':react-native-navigation'
project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-navigation/android/app/')

이곳은 외부 모듈을 우리의 프로젝트에 include할 수 있도록 세팅하는 곳으로, 만약 여러분이 외부 모듈을 이미 사용해보셨다면 다양한 include들이 있을것 입니다.

또한 1, 3번 줄은 기본적으로 세팅되어있는 사항이니 프로젝트 이름을 다르게 하였으면 변경하실 필요 없습니다.


두번째는 build.gradle입니다.

/TestProject/android/app/build.gradle에 위치하고 있습니다.

android/build.gradle이 아니라 android/app/build.gradle임을 주의하세요!


이곳에 아래의 내용을 추가해야합니다.

 android {
     compileSdkVersion 25
     buildToolsVersion "25.0.1"
     ...
 }

 dependencies {
     compile fileTree(dir: "libs", include: ["*.jar"])
     compile "com.android.support:appcompat-v7:23.0.1"
     compile "com.facebook.react:react-native:+"
     compile project(':react-native-navigation')
 }

수정된 파일은 아래와 같습니다.

apply plugin: "com.android.application"

import com.android.build.OutputFile

/**
 * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
 * and bundleReleaseJsAndAssets).
  ... 매우 긴 주석은 생랴크
 *   // supply additional arguments to the packager
 *   extraPackagerArgs: []
 * ]
 */

project.ext.react = [
    entryFile: "index.js"
]

apply from: "../../node_modules/react-native/react.gradle"

/**
 * Set this to true to create two separate APKs instead of one:
 *   - An APK that only works on ARM devices
 *   - An APK that only works on x86 devices
 * The advantage is the size of the APK is reduced by about 4MB.
 * Upload all the APKs to the Play Store and people will download
 * the correct one based on the CPU architecture of their device.
 */
def enableSeparateBuildPerCPUArchitecture = false

/**
 * Run Proguard to shrink the Java bytecode in release builds.
 */
def enableProguardInReleaseBuilds = false

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.1"

    defaultConfig {
        applicationId "com.testproject"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0"
        ndk {
            abiFilters "armeabi-v7a", "x86"
        }
    }
    splits {
        abi {
            reset()
            enable enableSeparateBuildPerCPUArchitecture
            universalApk false  // If true, also generate a universal APK
            include "armeabi-v7a", "x86"
        }
    }
    buildTypes {
        release {
            minifyEnabled enableProguardInReleaseBuilds
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
        }
    }
    // applicationVariants are e.g. debug, release
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // For each separate APK per architecture, set a unique version code as described here:
            // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
            def versionCodes = ["armeabi-v7a":1, "x86":2]
            def abi = output.getFilter(OutputFile.ABI)
            if (abi != null) {  // null for the universal-debug, universal-release variants
                output.versionCodeOverride =
                        versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
            }
        }
    }
}

dependencies {
    compile fileTree(dir: "libs", include: ["*.jar"])
    compile "com.android.support:appcompat-v7:23.0.1"
    compile "com.facebook.react:react-native:+"  // From node_modules
    compile project(':react-native-navigation')
}

// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
    from configurations.compile
    into 'libs'
}

35번 줄의 android와 78번 줄의 dependencies를 확인하실 수 있습니다.


다음은 MainActivity.java입니다.


/TestProject/android/app/src/main/java/com/testproject/MainActivity.java에 위치하고있습니다.

MainActivity.java는 기존 코드를 싹 뒤집어야합니다.

아래처럼 변경해주시면 됩니다.(주석은 수정하기 전 코드입니다.)

package com.testproject;

import com.reactnativenavigation.controllers.SplashActivity;

public class MainActivity extends SplashActivity {

}
/*
import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {

    @Override
    protected String getMainComponentName() {
        return "TestProject";
    }
}
*/

다음은 MainApplication.java입니다.

후~ 바꿀게 참 많지만 무작정 link 명령을 사용하는 것보다 이렇게 직접 해보시는게 나중에 큰 도움이 되실겁니다.


MainApplication.java는 MainActivity.java와 동일한 경로에 위치하고 있으므로 이미지는 첨부하지 않겠습니다.

또한 MainApplication.java도 아래 내용으로 뒤집어야합니다.

import com.reactnativenavigation.NavigationApplication;

 public class MainApplication extends NavigationApplication {

     @Override
     public boolean isDebug() {
         // Make sure you are using BuildConfig from your own application
         return BuildConfig.DEBUG;
     }

     protected List import com.reactna <ReactPackage> getPackages() {
         // Add additional packages you require here
         // No need to add RnnPackage and MainReactPackage
         return Arrays.<ReactPackage>asList(
             // eg. new VectorIconsPackage()
         );
     }

     @Override
     public List<ReactPackage> createAdditionalReactPackages() {
         return getPackages();
     }
 }

또한 아래의 내용도 추가해야합니다.

@Override
public String getJSMainModuleName() {
    return "index";
}

이 부분이 React Native 앱의 시작점을 index.js라고 명시해 놓은 곳 입니다.

React Native 0.49 버전 이전에는 index.android.js index.ios.js 라는 이름으로 index의 분기가 나눠져 있었지만 우리에겐 먼 나라의 이야기네요!


이렇게 수정된 코드는 아래와 같습니다.

혹시 모르니 기존 import 모듈은 남겨놓겠습니다.

package com.testproject;

import android.app.Application;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;

import java.util.Arrays;
import java.util.List;

import com.reactnativenavigation.NavigationApplication;
public class MainApplication extends NavigationApplication {

    @Override
    public boolean isDebug() {
        // Make sure you are using BuildConfig from your own application
        return BuildConfig.DEBUG;
    }

    protected List<ReactPackage> getPackages() {
        // Add additional packages you require here
        // No need to add RnnPackage and MainReactPackage
        return Arrays.<ReactPackage>asList(
            // eg. new VectorIconsPackage()
        );
    }

    @Override
    public List<ReactPackage> createAdditionalReactPackages() {
        return getPackages();
    }

    @Override
    public String getJSMainModuleName() {
        return "index";
    }
}


이제 마지막으로 AndroidManifest.xml입니다.


AndroidManifest.xml은 /TestProject/android/app/src/main/AndroidManifest.xml에 위치하고있습니다.

AndroidManifest.xml의 application을 아래와 같이 수정합니다.

 <application
     android:name=".MainApplication"
     ...
 />




4.iOS 세팅


(저는 iOS 프로젝트를 다뤄본적이 없어서 처음에 이 부분을 많이 헤매었습니다.

궁금한 부분이나 잘 안되는 부분이 있다면 댓글 남겨주세요!)


우선 /TestProject/ios/TestProject.xcodeproj를 더블클릭하여 Xcode로 우리의 프로젝트를 열어줍니다.

다음은 /TestProject/node_modules/react-native-navigation/ios로 이동합니다.

여기에는 아래 이미지처럼 ReactNativeNavigation.xcodeproj라는 파일이 존재하는데, 이를 Xcode의 Libraries안에 드래그하여 복사해줍니다


다음은 Link Binary With Libraries에 React Native Navigation 라이브러리를 추가하는 작업입니다.

아래 이미지를 따라 진행하시면 됩니다.




문제없이 진행되고 계신가요?

다음은 Header Search Paths를 설정해야합니다.



위 이미지를 따라하시고 아래의 텍스트를 입력하세요.

$(SRCROOT)/../node_modules/react-native-navigation/ios


마지막으로 AppDelegate.m을 수정해야합니다.

우리 불친절한 Wix 뽀이~♡는 이 부분을 문서에 넣어주지 않았습니다.


문서만 따라가다 실행하면 앱이 실행되다가 꺼져버리는 현상이 발생합니다.

이 문제에 대한 해결책을 예제 코드에서 찾을 수 있었죠

저처럼 iOS 앱을 만져본적 없는 분은 이런 현상에 어려움을 느끼셨을거라 생각합니다.

#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import "RCCManager.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSURL *jsCodeLocation;
#ifdef DEBUG
  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
  jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
  
  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  self.window.backgroundColor = [UIColor whiteColor];
  [[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation launchOptions:launchOptions];
  return YES;
}

@end

자세히 살펴보면 RCCManager.h를 import한 것과 하단 부분이 달라진 것을 볼 수 있습니다.

기존에 지레짐작 하기로는 RCTRootView로 실행하던걸 React Native Navigation을 담당하는 친구로 실행하도록 바꿔준 것 같습니다.



5.코드 수정 및 테스트


자 그럼 이제 React Native Navigation을 사용하여 화면을 띄워보도록 하겠습니다.

TestProject/index.js를  아래와 같이 수정합니다.

import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import { Navigation } from 'react-native-navigation';

//AppRegistry.registerComponent(appName, () => App);
Navigation.registerComponent('darr.App', () => App);

Navigation.startSingleScreenApp({
  screen: {
    screen: 'darr.App', // unique ID registered with Navigation.registerScreen
    title: 'Welcome', // title of the screen as appears in the nav bar (optional)
    navigatorStyle: {
      navBarHidden: false,
    }, // override the navigator style for the screen, see "Styling the navigator" below (optional)
    navigatorButtons: {} // override the nav buttons for the screen, see "Adding buttons to the navigator" below (optional)
  }
});

기존에 App.js를 띄워주던 AppRegistry 대신 Navigation.registerComponent를 사용하여 App.js를 등록하는 모습을 볼 수 있습니다.

이를 사용하여 앱을 실행하게 되며, 자세한 사항은 다음 글에서 이어나가도록 하겠습니다.


자 그럼 이제 각 플랫폼으로 실행해보겠습니다.


App.js는 지난 글에서 제작한 시작 화면과 동일한 상태입니다.

그런데 약간 달라진 점이 보이시나요?


앱의 화면에 타이틀 바가 생긴것이 보인다면 아주 잘 따라오신겁니다!


(짝짝짝)



6.요약


* react-native 0.55.4 / babel-preset-react-native 4.0.0으로 다운그레이드

* npm install react-native-navigation@1.1.472 --save

* 안드로이드 세팅(settings.gradle, app/build.gradle, MainActivity.java, MainApplication.java, AndroidManifest.xml)

* iOS 세팅(Libraries 추가, Link Binary With Libraries, Header Search Paths, AppDelegate.m)



7.마무리


link 명령이 편하긴 하지만, react native는 지속적으로 버전 업이 이루어지고 있어서 네이티브 구조가 새로 바뀌면 link를 완벽히 신뢰할 수 없습니다.

정말 긴 시간이였지만 이제 어떤 외부 라이브러리를 적용하시더라도 문제없이 설치하실 수 있을겁니다!


그럼 이제 본격적으로 React Native Navigation을 사용하여 화면 간 이동 및 다른 기능들을 알아보겠습니다.


Comming Soon!

3 Comments
댓글쓰기 폼