리액트 네이티브 코드푸시 - liaegteu neitibeu kodeupusi

안녕하세요. 휴먼스케이프 Software Engineer Dia입니다. 😀

모바일 앱은 웹 앱과는 다르게 업데이트 버전을 바로 배포할 수 없고 앱스토어와 구글 플레이 스토어 각 플랫폼의 심사를 거쳐 사용자에게 배포 되는데요, 앱 심사 기간은 짧게는 24시간 이내, 길게는 7일이 소요될 수 있으며, 다양한 이유로 심사 기간이 지연되거나 업데이트가 거부될 수 있습니다. 하지만 서비스 운영을 하다 보면 업데이트 내용에 따라 번거로운 심사 과정 없이 바로 배포가 이루어질 필요가 있는 경우가 있습니다.

휴먼스케이프의 레어노트앱은 React Native로 만들어지고 있는데요, React Native에서는 CodePush를 이용해 스토어 플랫폼 심사 없이 업데이트 버전을 바로 배포할 수 있습니다. 코드푸시는 리액트 네이티브의 큰 장점 중 하나라고 할 수 있습니다.

오늘은 레어노트 서비스에서 CodePush를 어떻게 사용하고 있는지 소개해 드리겠습니다.

CodePush란?!

CodePush는 MS에서 만든 오픈소스로 React Native나 Apache Cordova로 개발한 앱을 심사과정 없이 바로 업데이트 할 수 있도록 해주는 서비스입니다.

특정 업데이트를 중앙 저장소 역할을 하는 App Center에 JS Bundle과 assets을 업로드하면 모바일 클라이언트는 앱 시작 시 마다 업데이트 버전이 있는지 확인하여 동기화 합니다.

눈치채셨겠지만, 코드 푸시는 js 단에서의 변경 사항만 관리할 수 있으며, 네이티브 코드와 관련된 모든 변경사항(AppDelegate.m/MainActivity.java 파일의 수정, 라이브러리 추가 등)은 스토어 플랫폼을 통해 배포되어야 합니다.

CodePush 적용

본 포스팅 에서는 코드 푸시 라이브러리를 설치하고, 앱을 등록하는 세팅 과정은 생략합니다.

코드 푸시 업데이트 유형

코드 푸시는 mandatoryoptional 두 가지 유형의 업데이트를 제공할 수 있으며, 업데이트 유형에 따라 다른 업데이트 전략을 정의할 수 있습니다.

CodePush HOC 적용

코드 푸시는 코드에 대한 새로운 업데이트가 있는지 확인하고 새로운 변경 사항으로 앱을 다시 로드하는 CodePush의 HOC (고차 컴포넌트)에 Root Component를 래핑하는 것으로 간단하게 적용할 수 있습니다.

이 때 업데이트 유형에 따라 다양한 옵션을 정의할 수 있습니다.

// Wrapper function
codePush(rootComponent: React.Component): React.Component;
codePush(options: CodePushOptions)(rootComponent: React.Component): React.Component;

기본 옵션은 아래와 같이 적용됩니다.

  • 앱이 재시작(codePush.CheckFrequency.ON_APP_START) 될 때마다 코드 푸시 업데이트 체크
  • mandatory 업데이트는 즉시 다운로드 & 설치(codePush.InstallMode.IMMEDIATE)
  • optional 업데이트는 업데이트 파일은 다운로드 해 두었다가 다음번 앱이 재 시작 될 때 설치(codePush.InstallMode.ON_NEXT_RESTART)

추가적으로, updateDialog 옵션을 정의하여 업데이트가 있을 때 유저에게 업데이트를 즉각적으로 설치 할지에 대한 Alert를 띄울 수 있는데 이 경우 앱스토어 정책에 위반되어 리젝사유가 될 수 있으니 주의해야 합니다.

런타임 단계의 업데이트 방지

위와 같이 코드 푸시를 구성하게 되면 사용자가 앱의 첫 화면에 진입하여 사용하기 시작하던 중 갑자기 업데이트가 설치되어 앱이 재실행되는 경험을 하게 됩니다.

이는 코드 푸시의 비동기성 때문인데요, 불편한 사용자 경험을 개선하기 위해 코드 푸시 동기화가 완료된 후 앱에 진입할 수 있도록 useCodePush 커스텀 훅을 구성하여 코드 푸시 동기화 상태를 체크하도록 했습니다.

그리고 App.js에서는 코드 푸시 동기화 작업이 진행 중이면 동기화 진행 상태를 보여주는 뷰(SyncProgressView)를 렌더하고, 작업이 끝나면 앱의 첫 화면에 진입할 수 있도록 구성했습니다.

이 때, 동기화 진행 상황을 표시하지 않고 스플래시 스크린 아래에서 동기화 작업을 모두 진행할 수 있지만, 네트워크 상황 등에 따라 코드 푸시 동기화에 소요되는 시간이 오래 걸릴 수 있기 때문에 진행 상황을 알 수 있도록 SyncProgressView를 구성하여 더 나은 사용자 경험을 제공하도록 했습니다.

버저닝 규칙

코드 푸시를 활용할 때 유의해야할 점 중 하나는 버저닝 관리 입니다. 코드 푸시를 활용하면 업데이트를 두 가지 방법(스토어를 통해, 코드 푸시를 통해)으로 할 수 있기 때문에 각 버전이 혼동 되어선 안되며, 버전 분기도 사용자가 최종적으로 사용하고 있는 버전에서 진행되어야 합니다.

레어노트 에서는 major.minor.patch 버저닝 규칙을 사용하고 있는데, 코드 푸시 릴리즈는 binary version에는 영향을 미치지 않기 때문에 코드 푸시로 업데이트 되는 hotfix에 대해서는 major.minor.patch-hotfix로 표기하기로 하였습니다.

만약, 바이너리 버전 v1.0.0에 대해 업데이트 버전을 코드 푸시를 통해 릴리즈 한다면 그 버전은 v1.0.0–1가 됩니다. 여기서 또 한 번의 업데이트가 코드 푸시를 통해 릴리즈 된다면 그 버전은 v1.0.0–2가 되며, 이는 사용자가 최종적으로 사용하고 있는 버전인 v1.0.0–1에서 분기 및 테스트 되어져야 합니다.

추가적으로, 앱 센터에서 관리되는 코드 푸시 버전 넘버는 v20 과 같이 표기 되는데, 이는 코드 푸시만을 위한 버전 넘버로, 커스텀이 불가합니다. 이로인한 혼선을 피하기 위해 코드 푸시 릴리즈 시 --description옵션에 major.minor.patch-hotfix 버전을 표기하여 관리하고 있습니다.

정리

오늘은 리액트 네이티브 환경에서 코드 업데이트를 하는데 뛰어난 유연성을 제공하는 코드 푸시 도입에 대해 공유 드렸는데요, 코드 푸시 도입을 고려하고 계신 분들께 조금이나마 도움이 되었으면 하는 바람입니다. 감사합니다.

Walk with us!

기술이 세상을 더 아름답게 할 수 있다고 믿으신다면, 휴먼스케이프와 함께 소중한 뜻을 펼칠 수 있습니다.
함께 걸어가며 성장하실 분, 언제든지 연락해주세요 :)

휴먼스케이프 개발자 채용공고 보러가기

CodePush란?


코드 푸쉬는 MS에서 만든 오픈소스로서 앱을 심사없이도(앱 스토어 릴리즈없이)
실시간 업데이트를 가능하게 해주는 모듈입니다.
리액트 네이티브에서는 네이티브 코드와 설정이 아닌
JS단의 코드와 assets(이미지, 폰트등..)의 요소들을
앱 심사없이 바로 업데이트 할 수 있습니다.

단, 네이티브 영역의 코드가 변경되어야 한다면, 코드 푸쉬를 사용해서는 안됩니다.
네이티브단을 수정했음에도 코드 푸시를 하는 순간 앱은 아마도 비정상 종료될 것입니다.
(다시 패키징하는 방법으로 심사를 통해 배포해야합니다.)

가급적, 단순 로직 변경 및 문자열 변경, api 소스코드 변경정도의 js수정시에만
코드푸시를 사용하고, 라이브러리의 추가/삭제등..
네이티브의 설정이 변경되는 경우에는 코드푸시보다는 다시 apk 혹은 ipa를 추출하여
심사를 받는 것이 좋습니다.

CodePush 사용법


사용법에 대해서 사전에 미리 말하자면,
저는 React-Native에서만 Code-push를 통한 update를 이용해본 경험이 있으므로,
Meteor라던지 다른 환경에서의 Code-push는 이와 다를 수 있습니다.

(1) appcenter-cli 설치

$ npm install -g appcenter-cli

(2) react-native-code-push 설치

$ npm install --save react-native-code-push

(3) Appcenter 가입 후 앱 등록을 합니다.

만약 moong2라는 앱이 있다면 Android와 iOS 두개의 코드푸시를 만들텐데
moong2-ios, moong2-aos 요런식으로 뒤에 os를 달아주는 것이 좋습니다.
(왜냐하면 같은 이름으로 프로젝트를 생성못할 뿐더러 헷갈릴수 있기 때문입니다..!)

앱등록이 끝나면 앱의 distribute -> codepush로 이동합니다.
그러면 친절하게(?) 영어로 절차를 설명해주는데요!
절차대로 수행해줍니다.

(4) 절차의 첫번째인 SDK등록, iOS/Android Native 영역 설정해주기 : 하단 링크 참고
(https://docs.microsoft.com/ko-kr/appcenter/distribution/codepush/rn-get-started)

등록하는 과정에서 각각 배포키를 받는데 (staging, production)
이는 굉장히 소중한 것이므로 별도로 기록을 해두는 것이 좋습니다.
private.pem도 잘 보관해놓는 것이 좋습니다.

staging과 production을 어떻게 운영할 것인지는 개발자 본인의 마음이겠지만,
저의 경우 staging은 DEV환경, Production은 LIVE환경으로 나누어서
개발단에서는 staging 릴리즈로 테스트하고, 문제가 없을 경우
Production으로 옮겨서 배포하였습니다.

(5) react-native에 코드를 추가해줍니다.

import codePush from 'react-native-code-push';

const codePushOptions = {
  checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
  // 언제 업데이트를 체크하고 반영할지를 정한다.
  // ON_APP_RESUME은 Background에서 Foreground로 오는 것을 의미
  // ON_APP_START은 앱이 실행되는(켜지는) 순간을 의미
  updateDialog: false,
  // 업데이트를 할지 안할지 여부에 대한 노출 (잠수함 패치의 경우 false)
  installMode: codePush.InstallMode.IMMEDIATE
  // 업데이트를 어떻게 설치할 것인지 (IMMEDIATE는 강제설치를 의미)
};

// Wrap your root component with the codePush higher-order component:
App = codePush(codePushOptions)(App);

이렇게 하면 코드푸시 사용준비가 끝납니다.

(6) 코드푸시 배포

// production 배포시 

$ appcenter codepush release-react -a {ORGANIZER || USERNAME}/{APP_NAME} -d Production -k ~/private.pem

이렇게 배포를 하고 앱을 들어가보면
크게 달라진 것은 없지만 ON_APP_START일 경우,
앱 실행후 코드푸시 서버로부터 정보를 받아
업데이트가 필요한 경우 업데이트를 받습니다.

업데이트를 받고나면 앱이 재기동되고, 바로 코드푸시가 반영됩니다.


코드 푸시 사용후 느낀점


과거 라이브배포 단계에서 android는 잘 반영이 되었지만
iOS는 코드사이닝 문제로 애를 먹었었습니다.
그래도 문제가 해결되자 단순히 RN단 코드를 변경하는 것에 한해서
평소 3일정도 소요되는 업데이트 심사없이
앱에 코드를 반영시킬수 있다는 것이 매우 매력적이고 황홀했습니다.
하지만 코드푸시가 모든 것을 해결해 주진 못합니다.
앱스토어 업데이트와 코드푸시를 적재적소에 잘 활용해야 좋을 것 같습니다.