최근에 웹뷰인 React Native 앱 서비스를 다뤄보는 경험을 했다.

앱 내에서 웹뷰를 띄워보는 희귀한 경험을 해서 꼭 블로그에 작성해야겠다고 다짐했다!

 

 

앱 내에서 webview를 띄우면서 가장 중요하고자 어려웠던 부분을 기록하고자 한다.

 

보통 앱 내에서 사용자들의 히스토리를 기록하고 로깅하고자 로그 이벤트를 기록한다.

그러기 위해서는 웹뷰에서 일어난 이벤트들을 RN에서 감지해야 한다.

 

 

목차

1. React-native와 Webview 통신

2. 내가 처했던 문제 상황


React-native와 Webview 통신

1. Web -> RN

 

Web: postMesageKey 전달

 

RN: postMessageKey event를 받아 동작시킨다.

 

위의 예시에서 test data를 받도록 해놨는데, 만약 data를 넘겨줘야 한다면 위의 방식과 같이 넘겨주면 된다.

 

 

2. RN -> Web

 

RN: WebView 속성 중 onLoadStart를 사용해서 들어가자마자 data 전송

 

RN: data 안에 isPremium 값을 보내준다.

 

Web: Type 지정

 

Web: data 안에 우리가 지정해준 data 타입으로 받는다.

 


내가 처했던 문제 상황

What?

Web에서 RN에 데이터를 전송하는 것은 굉장히 easy 했다. 

위의 코드처럼 RN에서 Web으로 데이터를 전송할 때

웹뷰 로드가 시작되면 데이터가 바로 전송되야 했다.

하지만 RN 측에서는 데이터를 잘보내줌에도 불구하고, 웹뷰에서는 첫 렌더링 때 값을 가져오지 못하는 문제가 발생했다.

 

 

 

How Solve?

값을 보내주지 말고, localStorage에 저장한 후 웹뷰가 로딩될 때마다 가져오자!

 

RN에서 해당 값을 localStorage에 저장한다.

 

Web: didMount 될 때마다 localStorage에서 값을 가져온다.

 

이렇게 문제를 해결할 수 있었다. 

 

 

 

 

이 블로그를 통해 누군가 도움이 되었다면 그것만으로 만족스럽다 ㅎ

Fastlane 배포 자동화 적용 과정

 

항상 효율적으로 반복되는 시스템을 자동화하려고 하는데,

그 중 하나가 앱 배포 시간을 줄이기 위한 CI/CD 구축하는 것이다.

 

안드로이드는 배포할 때 생각보다 간단하지만,

iOS는 아카이브 한 후 앱스토어 커넥트에 업로드 한 뒤 심사 요청까지 거쳐야하는 귀찮고 번거로운 작업들이 발생한다.

 

좋은 코드를 만드는 것도 좋지만, 이러한 반복적인 코드들을 자동화하여 업무의 효율을 높이는 것이 중요하다.

 

예전에 적용했던 fastlane 자동화 일련의 과정을 공유해보려고 한다.

 


 

iOS 배포

  • Fastlane 설치
# ruby가 이미 설치되어 있다면 생략
$ brew install ruby

# Fastlane 설치
$ gem install fastlane
  • iOS 설정
# /ios
$ fastlane init
iOS fastlane init시 나오는 화면 (4가지 중 1개 선택)
  • 일단 테스트 플라이트에 올릴 때 사용하기 위해 2번을 선택했다.
    + 추가) 나중에 또 추가할 수 있으니 신중하게 선택 안해도 된다.

apple ID를 입력하라고 나오는데
애플 아이디 입력 후 숫자 6자리를 입력했더니 이런 오류가 나서, 다시 n을 누르니 이중 로그인 절차를 한 번 더 거치고 완료 됐다.

 
로그인 성공 시 Appfile과 Fastfile이 생성된다.
  • AppFile
app_identifier("your.app.identifier") # The bundle identifier of your app
apple_id("your-apple-id") # Your Apple email address

Appfile에는 app bundle id와 apple id가 포함되어 있는 걸 확인할 수 있다.

우리 팀은 애플아이디를 공유하고 있지 않고, 각자의 계정을 사용하고 있다.
각자가 배포할 때마다 apple id를 바꿔줘야하면 불편함이 생기므로 이것을 .env 를 이용해 환경변수로 관리한다.

 

  • .env
# /ios 루트 안에 해당 파일을 만든다
$ vim ./fastlane/.env
APP_IDENTIFIER="your.app.identifier"
APPLE_ID="my-apple-id@email.com"
...

이렇게 넣어준 뒤 Appfile을 수정하자.

app_identifier(ENV["APP_IDENTIFIER"]) # The bundle identifier of your app
apple_id(ENV["APPLE_ID"]) # Your Apple Developer Portal username
  • Fastfile

실제 커맨드 명령들을 만드는 파일이다.

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:ios)
platform :ios do
  desc "Push a new beta build to TestFlight"
  lane :beta do
     # ✅ 자동으로 빌드 넘버를 증가
    increment_build_number(xcodeproj: "xxxx.xcodeproj")
     # ✅ 빌드할 워크스페이스와 빌드 스키마 지정
    build_app(workspace: "xxxx.xcworkspace", scheme: "xxxx")
    # ✅ TestFlight 업로드
    upload_to_testflight(
      # ✅ 업로드 후에 App Store Connect 에 올라가기 전까지 시간이 걸리는데 이걸 기다리고 싶지 않다면 true 로 설정.
        skip_waiting_for_build_processing: true
      )
    slack(
      message: "Testflight 배포 성공",
      channel: "#r_frontend_deploy",
      slack_url:  ENV["SLACK_WEBHOOK_URL"],
    )
  end
  error do |lane, exception, options|
    slack(
      message: "에러 발생 : #{exception}",
      success: false,
      slack_url: ENV["SLACK_WEBHOOK_URL"],
    )
  end
  
end

 

  • SLACK_WEBHOOK_URL 설정

Slack에서 원하는 채널을 선택해서 Webhook을 설정하고, 해당 url을 통해서 요청을 보내면 해당 메세지가 전달되는 방식이다.

https://my.slack.com/services/new/incoming-webhook/ 에 들어가서 설정해보자

슬랙 채널을 하나 생성하고 추가를 하면 이렇게 웹후크 URL이 생성되는데 이를 복사하고, 설정 저장을 누르면 끝!

 

  • TestFlight 배포 설정

원래 실행 명령어는 fastlane ios beta이다. 하지만 우리가 플랫폼을 iOS로 미리 설정해두었기 때문에 이 명령어가 실행 가능하다.

fastlane beta

TIP. 명령어를 실행하기 전에 ios에서는 앱 암호를 발급받아 환경변수로 적용을 해줘야하고, android에서는 서비스 어카운트라는 것을 만들어 접근권한을 부여해주어야 한다.

 

마주했던 에러

error. 앱 암호 생성 에러

FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD variable

https://support.apple.com/ko-kr/HT204397 해당 링크를 들어가 암호를 설정하자.

앱 암호를 만들고 만들어진 암호를 입력해주면 된다.
이것은 매번 fastlane을 사용할 때 자동적으로 앱 암호로 로그인하게 하기 위함이다!

Could not store password in keychain

앱 암호는 ‘xxxx-xxxx-xxxx-xxxx’ 이렇게 만들어진 암호를 붙여넣으면 된다!
이 암호로 처음에 붙이면 에러가 나는데, 두번째 성공하면 upload가 된다. 이유는 모르겠음..!!

추가적으로 릴리즈를 위한 앱의 자동 배포까지 하고 싶다면
참고한 자료 https://gyuios.tistory.com/241 에서 확인해보자


 

Android 배포

$ cd my-project/android
$ fastlane init

작업하기 전 체크 사항

  • 구글 플레이스토어 어드민 계정 필요
  • Google Play Console에 앱 등록
  • Google Credential 발급

https://docs.fastlane.tools/actions/supply/ 에 들어가서 json 파일까지 발급 받아서 Appfile에 넣고 온다.


JSON Secret 설정 및 권한 부여

json 파일을 등록하고,
아래 명령어를 통해 다운받은 private key로 구글플레이스토어와 연결이 잘되는지 확인해보자.

$ fastlane run validate_play_store_json_key json_key:/path/to/your/downloaded/file.json
Google Play Store에 연결 성공

git에 올라가면 안되는 파일 .env나 json 파일은 .gitignore에 저장한다!

 

  • Firebase App Distribution 추가

테스트앱을 내기 위해서 Firebase App Distribution에 올리기 때문에 Firebase App Distribution까지 자동화까지 하기로 한다.

fastlane과 Firebase App Distribution 연결

# firebase_app_distribution을 설치한다.
fastlane add_plugin firebase_app_distribution

firebase 공식 문서를 보면 action으로 firebase_app_distribution_login이 있다는 데 해당 action이 없는 것을 확인했다.

궁금해서 해당 fastlane github issue 올렸더니..

버전 0.5.0에서 부터는 firebase_app_distribution_login이 deprecated가 되고, firebase CLI를 설치 해야한다고 한다.

# Firebase CLI를 전역적으로 설치한다.
$ npm install -g firebase-tools

# 설치 후 Firebase 계정에 로그인한다.
$ firebase login

위 단계를 완료하면 Firebase CLI를 사용하여 Firbase App Distribution과 같은 다른 Firebase 서비스를 사용할 수 있다.

 

  • Fastfile
  ##########################################
  ########## Firebase Distribution #########
  ##########################################
   
  buildDir = "./build"
   
  lane :firebase do
    firebase_app_distribution(
        app: ENV["FIREBASE_APP_ID"],
        # 그룹명은 firebase에 있는 group alias를 사용해야한다.
        groups: "tester_groups",
        debug: true
    )
    slack(
      message: "App Distribution 업로드 성공",
      # 해당 slack 채널명 사용
      channel: "#r_frontend_deploy",
      # iOS에서도 사용했던 웹 훅을 연결해준다. android도 .env 파일을 만들어줄 것
      slack_url:  ENV["SLACK_WEBHOOK_URL"],
    )
  end

  error do |lane, exception, options|
    slack(
      message: "에러 발생 : #{exception}",
      success: false,
      slack_url: ENV["SLACK_WEBHOOK_URL"],
    )
  end

+) 추가적으로 App Store 배포까지 해보자

desc "Deploy a new version to the Google Play"
    lane :playStore do
      releaseFilePath = File.join(Dir.pwd, "my-release-key.keystore")
      gradle(task: "clean")
      gradle(
        task: 'bundle',
        build_type: 'Release',
        properties: {
          "android.injected.signing.store.file" => releaseFilePath,
          "android.injected.signing.store.password" => ENV["STORE_PASSWORD"],
          "android.injected.signing.key.alias" => ENV["KEY_ALIAS"],
          "android.injected.signing.key.password" => ENV["KEY_PASSWORD"]
        }
      )
      upload_to_play_store(
          track: 'production',
          release_status: 'draft',
      )
      slack(
      message: "Google Play Store 업로드 성공",
      channel: "#r_frontend_deploy",
      slack_url:  ENV["SLACK_WEBHOOK_URL"],
    )
    end

releaseFilePath도 ./env 파일 내에 관리해도 된다. 
현재는 /fastlane 파일 안에 my-release-key.keystore를 저장해줬는데 두 번 저장되는 꼴이 되기 때문에 경로를 잘 설정하면 될 것 같다!

 

후기

그동안 하던 스프린트가 마치고, 시간이 남아 자동화 배포까지 적용 해보았다. 
처음에는 낯설었던 fastlane이 에러 코드만 보고도 어떤 부분이 문제인지 알 수 있을 정도로 익숙해졌다.

fastlane 자동화 시스템을 갖추는 데까지는 1~2일 소요되지만, 정말 업무 생산성을 높일 수 있는 시스템인 것 같다.
action으로 버전과 빌드 번호를 증가시키고 슬랙에도 푸시할 수 있으니 얼마나 편리한가 …!

이런 자동화 시스템을 잘 활용하여 업무 생산성을 높이기 위해 노력해야겠다.

프론트 CI/CD를 구축한 나, 제법 멋지군

상태 관리의 필요성

뭐가 중헌디

 

React에서 가장 중요한 상태값은

state가 많은 것은 문제가 되지 않지만, state가 많아지고 그것을 사용하는 컴포넌트의 갯수가 늘어날 수록 props drilling이 심해진다. 

state를 관리하지 않으면 여러 상태 값에 의해 컴포넌트가 재렌더링이 된다는 뜻이다.

 

state가 어디서 어떻게 변하는 지 한 눈에 파악하기 힘들어지고, 어떤 컴포넌트에 의해 상태 값이 변화했는지  손쉽게 파악하기 힘들다.

 

그래서 우리는 전역 상태를 관리해야 하고, React 컴포넌트 트리 안에서 데이터를 전역적으로 공유할 수 있는 상태 관리 라이브러리를 사용한다.

 

 

상태 관리 라이브러리

상태 관리 라이브러리는 다양하다. Context API, Redux, MobX, Recoil 등의 많은 도구가 있고, 각각의 장/단점이 분명하게 존재한다.

 

 

Context API

  • 가장 많이 쓰이는 상태 관리 라이브러리로 간단한 상태 관리만 필요하다면 쓰기 좋은 라이브러리이다.
  • 함수형 Hooks에서는 기능이 제한되어 있다.
  • 성능과 기능이 적다.

=> 간단한 프로젝트에 사용하기 좋은 상태 관리 라이브러리이다.

참고 자료 https://ko.legacy.reactjs.org/docs/context.html

 

Redux

  • 1개의 root & 1개의 store 만이 존재하고, 순수 함수(pure function)에 의존한다.
  • MVC 패턴의 문제점을 극복하고자 페이스북에서 고안한 상태 관리 도구이다.
  • 상태가 읽기 전용이다. 🥶
    • 지속적으로 불변성을 유지해야 하기 때문에 immutable.js와 같은 라이브러리를 사용해서 상태 값을 바꿔야 한다.
  • 상태 값의 변경 사항을 Redux Devtools를 이용해 직관적으로 볼 수 있는 방법이 있다.
    • 상태 값이 많아질 경우 디버깅 할 때 굉장히 유용할 것이라고 생각한다.
  • 작은 상태 하나를 변경하려고 해도, actions, reducer, type 등 여러번 작성해야하는 번거로움이 있다.
    • 그렇기 때문에 상대적으로 더 많은 양의 코드를 작성해야 한다.

Recoil

Recoil은 페이스북에서 만든 React를 위한 상태 관리 라이브러리이다. 

  • React의 useState와 비슷하게 동작하여, 러닝 커브가 적고 직관적인 구조로 구성되어 있다.
    • useState -> useRecoilState
    • state -> useRecoilValue
    • setState -> useSetRecoilState
  • 비동기 처리를 기반으로 작성되어 동시성 모드(concurrent mode)를 제공하기 때문에, Redux와 다르게 비동기 처리 라이브러리에 의존할 필요가 없다.
  • 단점으로는 Redux처럼 안정적인 Devtool이 없다.
    • Snapshot을 사용하면 RecoilRoot 컴포넌트 안에서 사용하는 모든 Recoil 상태에 접근이 가능하지만, 직관적으로 볼 수 있는 것은 아니고 콘솔을 이용하는 형태로 볼 수 있다.

=> 확실히 React를 만든 Facebook에서 직접 만든 것이라 다른 상태 관리 라이브러리보다 호환성이 좋다고 생각한다. 
뿐만 아니라, 러닝 커브가 적어 세팅하기도 편하다.

 

참고자료

https://recoiljs.org/ko/docs/introduction/getting-started/

구글 애드센스 (google adsense)

  • 웹사이트를 위한 것

 

애드몹 (admob)

  • 모바일 앱을 위한 것

 

구글 애드센스 정책 하나하나 살펴보며 시간 버린 나같이,,
그런 사람이 없었으면 하는 바람에서 글을 쓰게 됐다.

 


 

질문: react-native 앱에서 웹뷰를 띄워 모바일 애드센스 광고를 추가할 수 있나?

답변: 간단하게 말하면 '없다'

 

하지만, 본인이 크로스 플랫폼이 아닌 네이티브 플랫폼 개발자라면 가능하다!

  • webview API를 사용하면 구글 애드센스 광고를 추가할 수 있다.
  • 단 android, iOS만 가능하다.

https://support.google.com/adsense/answer/11893859?hl=ko&ref_topic=28893&sjid=5870858233913781273-AP

 

 

 

정책 참조 사이트
https://support.google.com/adsense/thread/194709442/앱-서비스-제공자-애드센스-승인-관련-문의?hl=ko

 

앱 서비스 제공자 애드센스 승인 관련 문의 - Google AdSense 커뮤니티

 

support.google.com

 

?: 앱 크기가 최적화랑 상관이 있나요?
🧚‍♀️: Yessss!

 


앱 크기가 작아야 하는 이유

  • 용량 때문에 18.7% 사람들이 앱을 제거한다. 개발자는 항상 번들 크기와 앱 용량을 줄이는 데 주의를 기울여야 한다.

 

 

WHAT?

코드의 전체 크기에 영향을 미치는 가장 큰 요소 중 하나는 라이브러리이다.

여러 라이브러리를 비효율적으로 사용하고, 원하는 디자인을 위한 커스텀 폰트를 넣다보면 앱의 크기가 커져 앱 속도가 저하됨.

native 앱과 달리 react-native 에서는

1. 메모리에 로드해야 하는 javascript bundle이 포함되어 있음.

2. 로드 후 javascript vm에 의해 구문이 분석되고 실행됨.

번들에 사용되지 않는 코드를 제거해주는 tree shaking 조차 native와 달리 react-native에서는 지원하고 있지 않는다.

tree shaking을 지원하지 않는 건 총 시작 시간에 부정적인 영향을 미칠 수 있음!!

 

 

 

앱이 실행될 때까지 걸리는 시간(로딩시간)은 스마트폰의 속도 성능을 나타내는 주요 지표 중 하나이다.

 

이건 있을 수 없어!!!

 

 

HOW?

앱 번들 크기를 분석해보자.

react-native에서는 react-native-bundle-visualizer 라이브러리를 사용해서 번들에 추가된 라이브러리의 세부 정보들을 파악할 수 있다.

가장 많이 차지하는 것들을 비교해보자!

 

 

라이브러리 설치 & 적용

https://github.com/IjzerenHein/react-native-bundle-visualizer

 

 

1. 라이브러리 설치

yarn add --dev react-native-bundle-visualizer

 

2. 실행

yarn run react-native-bundle-visualizer

 

3. 실행 후 화면

빌드하면 이런 창이 뜨는 걸 확인 할 수 있다!

ex)


아래는 실제 적용 화면 🙌

가장 큰 용량을 차지하는 라이브러리를 확인할 수 있다.

date-time-format-timezone... 

너가 뭔데,, 

 

 

알고보니 이거 현재 제대로 쓰이지도 않는 라이브러리였음..!ㅋ

 


찾아보면서 알게된 사이트 공유 ㅎㅎ
https://bundlephobia.com/ 
들어가서 라이브러리와 버전을 쓰면은 bundle 크기를 알려준다..!!
번들포비아 ㅋㅋ 이름도 웃기다. 잘 지었어 🤔
 

Bundlephobia | Size of npm dependencies

Bundlephobia helps you find the performance impact of npm packages. Find the size of any javascript package and its effect on your frontend bundle.

bundlephobia.com

 

 

 

 

오늘도 이렇게 최적화를 위해 힘써보았다.
유저에게 만족스러운 앱이 되는 그날까지,,
to be continue..

 

요즘 rn 앱 내에서의 ui re-rendering을 막으면서 최적화를 위해 힘쓰고 있다.

여러 방법들을 공부하면서 기록하고자 한다. 📚

 

프로파일링 하며 시도했던 방법들은 추후에 더 기록할 예정이다 🔥

 

 

1. 과도한 커스텀 hook 사용 방지

첫 번째로 우리 앱에는 hook이 정말 많았다.

종속성이 변경되면서 필요하지 않는 화면까지 re-rendering 되는 상황이었다.

 

최적화를 해보자

 

- 과도한 hook 사용을 방지하고 필요한 화면. 즉, 해당 컴포넌트 안에서만 hook을 사용하는 방식이다.

예를 들어 하나의 화면 안에 2가지 컴포넌트로 분리되어 있다고 치자. 

 

 

기존에는 presentational and container 패턴을 사용하면서 1과 2 화면에서 필요한 정보들을 모두 1+2 <전체> 화면에서 갖고 있었다. 
그렇다보니 props drilling이 많이 일어나 사용자 경험이 안좋아졌다.

 

 

필요한 화면에서만 hook을 사용해야겠구나!

 

 

해당 화면에서만 hook을 사용하도록 리팩토링하기 시작했다.

 

 

문제점

1. 1+2 <전체> 화면에서 화면 1에서만 사용되는 hook이 필요하다.

2. 과연 해당 hook이 전체 화면에서도 필요한 걸까?

 

단, 해당 커스텀 hook이 전체 화면에서는 불필요한 정보들이 많을 때를 기준으로 작성되었다.

 

 

 

해결법

필요한 정보들만 빼오자!

각각 화면에서 hook을 사용하되 컴포넌트를 그릴 때 필요한 정보만 전역으로 관리하자! 

전역으로 저장된 정보를 가지고 1+2 <전체> 화면에서 사용하면 되는 것이다 !!!

 

문제 상황

react-native-draggable-flatlist 라이브러리를 사용하는데 해당 데이터가 모두 잘 넘어옴에도 불구하고,
renderItem에서 item을 사용하려고 하면 모든 아이템이 렌더되지 않는 상황이 발생했다.

ex) the FlatList does not render all 100 items on initial load.

 

 

 

 

 

 

해결 과정

하나하나 디버깅을 해보자.

item을 찍어봤을 때 처음에는 반 정도 렌더링이 되고, 드래그를 하니 나머지 아이템이 렌더링 되는 상황을 목격했다.

음, 이건 해당 라이브러리 문제라고 생각해서 해당 라이브러리 이슈를 살펴보았다.

 

역시 나랑 똑같은 문제를 겪고 있는 사람이 있었다.

해당 이슈: https://github.com/computerjazz/react-native-draggable-flatlist/issues/453

 

Performance Issue on long lists · Issue #453 · computerjazz/react-native-draggable-flatlist

Hi, I'm facing a huge performance issue with my list. Currently, I'm rendering around 100 fields and the ui just freezes on drag.

github.com

 

 

 

 

문제 해결

처음 렌더링이 될 때 아이템의 갯수를 세팅해준다. 

해당 라이브러리에 initialNumToRender 속성을 사용하자.

initalNumToRender 속성

다시 확인해보면, 모든 아이템이 렌더링 되는 것을 확인할 수 있다.

뿌-듯 👍🏻

 

며칠 전 xCode를 14.1로 업데이트했는데,
 
error Could not get the simulator list from Xcode. Please open Xcode and try running project directly from there to resolve the remaining issues.
Error: Command failed: xcrun simctl list --json devices
xcrun: error: unable to find utility "simctl", not a developer tool or in PATH
에러가 났다.



 
원인
xCode 신규버전을 설치하고 나서 command line tool이 초기화되지 않아서 생기는 문제


2가지 해결법
1. Xcode - Preferences - Locations에서 "Command Line Tools"를 설정해주면 됨.

2. 터미널에서 해당 명령어 사용

$ sudo xcode-select --reset


해결했다!

+ Recent posts