ReactNativeで環境ごとのビルド設定を整える(iOS編)
ネイティブアプリ開発では、任意の環境に接続されたアプリを手元で動かしたり配布したりできる必要があります。これを構成する要素は2つあると思っています。
1つ目、iOS/Androidアプリのビルド設定は基本的には
- Debugビルド
- Releaseビルド
の2種類が存在します。Debugビルドは手元のマシンで実行して手元の端末で利用することが多いです。Releaseビルドだと一般的には配布用に最適化された形でビルドされます。Webで言う NODE_ENV=development, production
みたいなものですね。
2つ目として、接続先のバックエンドは
- ローカル環境(local)
- 開発環境(dev)
- 本番環境(prod)
が存在することが多いでしょうか(ネイティブアプリ向けのローカル環境は用意していないこともあるかもしれません)。
この記事では、ReactNativeを活用したiOS/Androidアプリ開発において、上記2要素を組み合わせた
- Debugビルド × local(手元での開発)
- Debugビルド × dev(手元での開発)
- Releaseビルド × dev(社内で開発環境を活用した動作確認)
- Releaseビルド × prod(ストアでの配布)
のようなことをできるようにすることを目指します。
前提
以下の環境を利用しています。
- react-native: 0.64.1
- Xcode: 12.5.1
- Android Studio: 4.2.1
プロジェクトの初期化は以下のコマンドで行います。
$ npx react-native init SampleApp --template react-native-template-typescript
AnalyticsやCrashlyticsなどの用途でFirebaseも導入することとします。
方針
envファイルは <root>/env
ディレクトリ以下に、 .env.local
、.env.dev
、.env.prod
のような形で作成します。
外部の接続情報(APIのエンドポイントなど)は可能な限り、ビルド時に注入する形にします。これを簡単にしてくれるパッケージとして react-native-config
があるのでこれを活用していきます。
ビルド時に ENVFILE=.env.dev
のような環境変数を渡せば、TypeScriptから読み取れるだけでなく、設定次第ではiOS/Androidのビルド時にもこの環境変数を利用できるようになります。
iOSの設定
パッケージの導入
ゴールとしては $ ENVFILE=env/.env.dev npx react-native run-ios
のようにコマンドを実行すれば
-
ENVFILE
で指定した環境変数に基づいた接続先につながる -
Google-Service-Info.plist
が切り替わる - bundleIdentifierが切り替わる
- (ついでに)アプリ名も切り替わる
ようになることを目指します。Xcodeの Build Configurations や scheme はできるだけ増やさないシンプルな構成でやっていきます(ReactNativeのバージョンがあがったり、Xcodeのバージョンが上がった際にDebug/ReleaseのBuild Configurationであることを前提に設定を書き換えて来ることがあるためです)。
react-native-config
のREADMEに結構詳しく書いてくれているので概ねそれに従って設定していきます。まずはパッケージをインストールします。
$ npm i react-native-config
$ cd ios && pod install && cd ..
Google-Service-Info.plist
ネイティブアプリでFirebaseを使う際に面倒なのが Google-Service-Info.plist
の存在です。環境ごとに用意する必要があるので Google-Service-Info-dev.plist
のような名前でプロジェクトに配置するものの、 Google-Service-Info.plist
というファイル名では位置されている前提で実行されるライブラリが存在します(Googleサインイン系のライブラリでそうだったことがある)。そのため、ビルド時に Google-Service-Info-dev.plist
を Google-Service-Info.plist
としてコピーする処理を用意する必要があり、すなわちビルド時に ENVFILE=.env.dev
のように渡された環境変数を利用できるようにする必要があります。
READMEに書かれている Availability in Build settings and Info.plist の設定に従います。簡単に説明すると
-
Config.xcconfig
を作成して1行目に#include? "tmp.xcconfig"
と書く -
.gitignore
にtmp.xcconfig
を追記する - Xcodeの
PROJECT
のConfigurations
のDebug
やRelease
のNone
になっているところをConfig
にする(1で作成したConfig.xcconfigが読み込まれるようにしている) - SampleApp の Edit scheme を開いて Build -> Pre-actionsに 以下のRun Scriptを追加する
"${SRCROOT}/../node_modules/react-native-config/ios/ReactNativeConfig/BuildXCConfig.rb" "${SRCROOT}/.." "${SRCROOT}/tmp.xcconfig"
です。やっていることは ENVFILE
の内容が tmp.xcconfig
に転記されるようにBuildのPre-actionに設定しているだけです。これをすることで Info.plist
などで環境変数の内容が使えるようになります。$(ENV_KEY)
のようにすれば利用できます。これにより、bundleIdentifierやアプリ名にsuffixをつけられるようになります。さらに、先程述べていたFirebaseの Google-Service-Info.plist
を動的にコピーできるようになります。
- XcodeでTARGETSをSampleAppにして、Build Phasesを開く
- +を押してNew Run Script Phaseを選ぶ
- Run Script ってなってる部分をダブルクリックすると名前が変えられるので変えて位置を
Check Pods Manifest.lock
の下にする - 以下のスクリプト書き込む。
rm -rf "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
cp "$SRCROOT/PrologueApp/GoogleService-Info-${APP_ENV}.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
最終的には 👇 のような感じになりますね。
スクリプトは ENVFILE
に APP_ENV
という環境変数が設定されている前提になっています。 僕が設定するときには APP_ENV
として接続先の環境を書くようにしていて、今回のケースだと local or dev or prod
になります。この APP_ENV
を 👇 の画像のように Bundle Identifier の設定にも活用すれば動的に変更することができます(もちろん APP_ENV
っていう名前じゃなくて、 BUNDLE_ID_SUFFIX
とかでもいいと思います)。Bundle IdentifierとGoogle-Service-Info.plistに設定されているBundle Identifierは一致している必要があることにご注意ください。
同様に、 Info.plist
の Bundle display name
の値を $(DISPLAY_NAME_SUFFIX)
のようなものを使って SampleApp$(DISPLAY_NAME_SUFFIX)
のように設定しておけば、 ENVFILE
の値に応じたアプリ名になります( $(DISPLAY_NAME)
として名前全体を設定できるようにしてもいいかもしれません)。
ここまでくれば、あとは AppDelegate.m
にFirebaseの初期化のコードを書けば以下のコマンドで動作します。
$ ENVFILE=env/.env.dev npx react-native run-ios
実機での実行
実機での実行が、ENVFILEを使ったいいやり方をまだわからなくて、本当は $ ENVFILE=env/.env.dev npx react-native run-ios --device=foo
みたいな感じで実機実行できるといいんですが、おそらく?現状できないので仕方なく scheme を作って対応しています。
- Xcodeで SampleApp の Edit scheme を開く
- 左下のほうの
Duplicate Scheme
を押す - 名前は ENVFILEで指定するenvの名前(devとか)にしつつ、下の方にある
Shared
のチェックを入れる(git管理されます) - 左側の Build -> Pre-actionsで New Run Script して、
cp "${PROJECT_DIR}/../env/.env.dev" "${PROJECT_DIR}/../.env"
を書く
これで作成された dev
schemeを選択後、転送先を実機してRunすれば実機で実行できます。別のファイル使いたくなったらまたschemeを増やしてcp元のenvファイルを変えれば実行できます。
(ここをもうすこしシンプルにいい感じに設定できるといいんですが…)
iOSアプリの配布
すみません…ここも書こうと思っていたんですが途中で力尽きました…ひとまずキーワードをおいておきます
- fastlane/fastlane matchでの証明書管理
- AppStore Connect API
- ビルド時に
ENVFILE
を指定する
おわりに
ENVFILE
を指定して実行するだけで、接続先やGoogle-Service-Info.plistを動的に切り替えられるようになりました。本当は
- Android編
- Firebase App Distributionへの配布編
- ストアへのリリース編
- CD編
も書きたかったのですが、iOSの設定だけで力尽きてしまいました…。みなさんの反応がよければ、Zennの本などにまとめることも検討していますのでお気軽にフィードバックいただければ幸いです。
僕自身は Flutter で同様の内容で開発環境やCD環境のセットアップ、ベースコードの実装などを顧問業としてやっていたこともあり、React Nativeでのセットアップもそれほど大変ではありませんでした。クロスプラットフォームでの開発をやっていた経験が別のクロスプラットフォームでも活きるのは少しおもしろかったです。
Discussion