はじめに
こんにちは、とりかつ(@torikatsu923 )です。
アプリ開発 をしていると本番、ステージング、開発と環境を分けたい場合があります。
Flutterでアプリの環境を切り替える方法として、今まではFlavorを使う方法がありました。
最近、DartDefinesという仕組みでも環境を切り替えることができるようになりました。
Flavorと比べ、DartDefinesを使った方が設定がシンプルになります。
今回の記事では目的、OS別(android , iOS )にDartDefinesを利用したアプリの環境切り替え方法を紹介します。
この記事の読み方
この記事では基本設定が終わっている前提で各環境の切り替えの解説をします。そのため、初めに基本設定を読むことを強くお勧めします。
基本設定の後はどの順番で読んでいただいても大丈夫です。
この記事の読み方
環境切り替えの対象
アプリアイコン
アプリのID(bundleId, applicationId)
アプリの表示名
Firebaseの設定ファイル(GoogleService-Info.plist, google -services.json )
Dart のコード中の設定
サンプルコード
サンプルコードは以下のリポジトリ です。切り替え対象を全て切り替えられるよう設定した状態になります。
github.com
目次
基本設定
この章ではネイティブ側でDartDefinesの値を利用できるようにする方法を解説します。この基本設定が終わると以下のようにflutter run
で環境を指定できるようになります。
$ flutter run --dart-defines=FLAVOR= ${ 環境 }
環境
切り替える環境は以下の3つです。
環境
FLAVOR
開発
dev
ステージング
stg
本番
prd
iOS では環境ごとに.xcconfig
を用意しておき、ビルド時にDartDefinesの値から読み込む.xcconfig
を変更する方法をとります。
1. 環境に対応するxcconfigを作成する
以下の.xcconfig
をRunner > Flutter
に作成します。
dev.xcconfig
stg .xcconfig
prd.xcconfig
Runner > Flutter
を右クリックし、New File...
を選択します。
NewFile
を選択するとモーダルが表示されるため、Configuration Settings File
を選択します。
選択後Save As
を作成したいファイル名に変更し、create
を押してファイルを作成します。
作成したxcconfig
の中身は以下のようにします。
FLAVOR=devまたはstgまたはprd
dev.xcconfig
の場合はこのようになります。
次に環境ごとのxcconfigをincludeできるようDebug.xcconfig
とRelease.xcconfig
に以下を追記します。
#include "DartDefine.xcconfig"
この後、ビルド時にFlavorに対応するxcconfig
をincludeするDartDefine.xcconfig
というファイルを動的に生成するようにします。そのためここではDartDefine.xcconfig
をincludeするようにしています。
2. DartDefinesに対応するxconfigをincludeするシェルスクリプト を作成する
以下の内容のファイルをios/scripts/dart_define.sh
に置きます。
echo $DART_DEFINE | tr ' , ' ' \n ' | while read line;
do
echo $line | base64 -d | tr ' , ' ' \n ' | xargs -I @ bash -c " echo @ | grep 'FLAVOR' | sed 's/.*=//' "
done | (
read flavor
echo " #include \" $flavor .xcconfig \" " > $SRCROOT /Flutter/DartDefine.xcconfig
)
DartDefinesの値は$DART_DEFINES
でアクセスできます。
また$SRCROOT
はios
ディレクト リのパスを指しています。
以下のように指定した場合、$DART_DEFINES
で得られる値は次のようになります。
--dart-defines=A=valueA,FLAVOR=dev,B=valueB
QT1hLEZMQVZPUj1kZXYsQj1i,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==
どうやら実行時に指定した値以外にFLUTTER_WEB_AUTO_DETECT=true
をbase64 エンコード した値がカンマ区切りで追加されるようです。
この文字列からFLAVORの値を取り出し、include対象を指定したxcconfigを吐き出すため、ワンライナー で以下の処理をしています。
文字列をカンマ区切りる
カンマで区切った各文字列をbase64 でデコードし、カンマで区切る
FLAVOR=xxx
が見つかったら=
より右側の値を取り出す
flavorに対応するxcconfig
をinclude
するDartDefine.xcconfig
というファイルを生成する
3. ビルド前に2.のスクリプト が走るようPre-actionを設定する
Runner > edit schema...
を選択しモーダルを表示します。
Build > Pre-action
を開き以下を設定します。
Provide build setting from
Runner
スクリプト
sh "$SRCROOT/scripts/dart_define.sh"
4. .gitignoreの編集
ビルドのたびに差分が発生しないよう.gitignore
に以下を追記します。
.gitignore
DartDefine.xcconfig
iOS でDartDefinesを受け取る基本設定は以上になります。
android ではbuild.gradle(app)でDartDefinesから値を取り出して変数に代入する方法をとります。
android/app/build.gradle
に以下を追記します。追記する場所はどこでもいいですが他の環境切り替えの設定でandroid.defaultConfig
でDartDefinesの値を利用することを考えると、android { ...
よりも前にする必要があります。
def dartEnvironmentVariables = project
.property('dart-defines')
.split(',')
.collectEntries {
new String(it.decodeBase64(), 'UTF-8')
.split(',')
.collectEntries {
def pair = it.split('=')
[(pair.first()): pair.last()]
}
}
DartDefinesの値はproject
経由でアクセスすることができます。DartDefinesはbase64 エンコード された文字列がカンマ区切りになっているため、split(',')
してさらに各値をsplit('=')
することでDartDefinesに渡された値全てを含むmapを作成しています。
[(pair.first()):pir.last()]
よりmapのキーはDartDefinesに指定した変数名になるため、FLAVORの値は以下のようにアクセスできるようになります。
dartEnvironmentVariables.FLAVOR
android の基本設定は以上になります。
アプリアイコン
本節ではflutter_launcher_icons で自動生成したアイコンを使用して環境ごとにアイコンを切り替える方法を紹介します。
アイコンの自動生成
初めにpubspec.yaml
にflutter_laucher_icons`の依存関係を追加します。
...
dev_dependencies :
flutter_test :
sdk : flutter
flutter_lints : ^1.0.0
flutter_launcher_icons : ^0.9.2
...
依存関係を追加したら以下を実行します。
$ flutter pub get
次に環境ごとのアイコン画 像を用意します。今回は以下の3つの画像を用意してassets/launcher_icons/
に置いておきます。
画像が用意できたら環境ごとの設定ファイルを用意します。今回は以下の3つを用意しました。
flutter_launcher_icons-dev.yaml
flutter_launcher_icons-stg .yaml
flutter_launcher_icons-prd.yaml
ファイル名はflutter_launcher_icons-${環境名}.yaml
とする必要があり、置く場所はpubspec.yaml
と同じ階層にする必要があります。
それぞれのファイルの中身は以下のようにします。以下はdevの例で、アイコン画 像のパス(image_path
)は環境ごとに変えておく必要があります。
flutter_icons :
android : true
ios : true
image_path : "assets/launcher_icons/dev.png"
さらにエラーが出てしまうことを防ぐため、pubspec.yaml
の一番最後に以下を追記します。
flutter :
uses-material-design : true
flutter_icons :
android : true
ios : true
最後に以下を実行してアイコン画 像を自動生成します。
$ flutter pub run flutter_launcher_icons:main
このコマンドによってandroid はandroid/app/src/${環境}/res
に、iOS はios/Runner/Assets.xcassets/AppIcon-${環境}.appiconset
にアイコンが生成されました。
あとはネイティブ側ビルド時に使用するアイコンを切り替えればOKです。
iOS では使用するアイコンを変数によって切り替えることでアイコン切り替えを実現します。
xcode でTargetのRunner > Build Settings
を開き、Primary App Icon Set Name
を以下に書き換えます。
AppIcon-$(FLAVOR)
これでiOS のアイコンを切り替えることができるようになりました。
android ではビルド時に環境に対応するアイコンをandroid/app/src/main/res/
にコピーすることでアイコンを切り替えます。
android/app/build.gradle
を開き以下を追記します。追記する場所はdef dartEnvironmentVariables
とandroid { ...
の間である必要があります。
task copyIcons(type: Copy) {
from "src/${dartEnvironmentVariables.FLAVOR}/res"
into 'src/main/res'
}
tasks.whenTaskAdded {
it.dependsOn copyIcons
}
copyIconsでは標準で用意されているcopy
タスクを使用しています。from
にコピー元、into
にコピー先を指定するだけでファイルをコピーするタスクを簡単に定義することができます。
タスクを定義したらwhenTaskAdded
でタスクの依存関係にcopyIcons
を追加します。これによって何かしらのタスクが実行された際にアイコンをコピーするタスクを実行することができるようになります。
whenTaskAdded
が既に定義されている場合、既存のwhenTaskAdded
のクロージャ 内にit.dependsOn copyIcons
を追記してください。
ビルドのたびに差分が発生しないよう.gitignore
に以下を追記します。
.gitignore
**/android/app/src/main/res/mipmap-*/ic_launcher.png
これでandroid でもアイコンを切り替えることができるようになりました。
アプリのID(bundleId, applicationId)
本節では変数を利用してアプリのIDを切り替える方法を紹介します。
まず各環境に対応するxcconfig
にAPP_ID_PREFIX
という変数を定義します。今回はdev, stg , prdと3つの環境に対応するために以下のようにしました。
dev.xcconfig
# ...
APP_ID_PREFIX=dev.
stg .xcconfig
# ...
APP_ID_PREFIX=stg.
prd.xcconfig
# ...
APP_ID_PREFIX=
次にxcode でTARGETS > Runner > Build Settings
を開き、Product Bundle Identifier
で
APP_ID_PREFIX
を参照するようにします。サンプルのアプリIDはcom.example.flutterenv
のため、以下のようになります。
$(APP_ID_PREFIX)com.example.flutterenv
これでiOS のアプリIDを切り替えられるようになりました。
android にはapplicationIdSuffix
が用意されています。ここに値を設定することでapplicationIdにサフィックス を追加することができます。
android/app/build.gradle
を開いて、defaultConfig
の最後に以下を追記します。
android {
// ....
defaultConfig {
// ...
if(dartEnvironmentVariables.FLAVOR == 'dev') {
applicationIdSuffix ".dev"
} else if(dartEnvironmentVariables.FLAVOR == 'stg') {
applicationIdSuffix ".stg"
} else if(dartEnvironmentVariables.FLAVOR == 'prd') {
applicationIdSuffix ""
} else {
throw new GradleException("unknown flavor" + dartEnvironmentVariables.FLAVOR + "has passed")
}
}
これでandroid でアプリIDを切り替えることができるようになりました。
アプリの表示名
本節では変数を参照することでアプリの表示名を切り替える方法を紹介します。それぞれのOSで以下のようにアプリの表示名が切り替わるようにします。
dev.${アプリ名}
stg .${アプリ名}
${アプリ名}
まず各環境に対応するxcconfig
にAPP_ID_PREFIX
という変数を定義します。今回はdev, stg , prdと3つの環境に対応するために以下のようにしました。(アプリIDの手順を先に行っている場合はこの手順は飛ばしてください。)
dev.xcconfig
# ...
APP_ID_PREFIX=dev.
stg .xcconfig
# ...
APP_ID_PREFIX=stg.
prd.xcconfig
# ...
APP_ID_PREFIX=
次にxcode でTARGETS > Runner > Info
を開き、Bundle display name
でAPP_ID_PREFIX
を参照するようにします。サンプルのアプリの名前はflutterenv
のため、以下のようになります。
$(APP_ID_PREFIX)flutterenv
これでiOS で環境ごとに表示名を切り替えることができるようになりました。
初めにandroid/app/build.gradle
を開いて、defaultConfig
の最後に以下を追記します。
android {
// ....
defaultConfig {
// ...
if(dartEnvironmentVariables.FLAVOR == 'dev') {
manifestPlaceholders += [appNamePrefix:"dev."]
} else if(dartEnvironmentVariables.FLAVOR == 'stg') {
manifestPlaceholders += [appNamePrefix:"stg."]
} else if(dartEnvironmentVariables.FLAVOR == 'prd') {
manifestPlaceholders += [appNamePrefix:""]
} else {
throw new GradleException("unknown flavor" + dartEnvironmentVariables.FLAVOR + "has passed")
}
}
manifestPlaceholders
に環境ごとのappNamePrefix
を追加します。こうすることでAndroidManifest.xml
からbuild.gradleで追加した変数を利用することができます。
以下のように代入してしまうとflutterのプラグイン 中で追加された変数を上書きしてしまうためビルドに失敗します。
manifestPlaceholders = [appNamePrefix:""]
次にandroid/app/src/main/AndroidManifest.xml
を開き、android:label
でgradleで追加した変数を使用するようにします。
<manifest
...
<application
android:label="${appNamePrefix}flutterenv" // この行を編集
android:name="${applicationName}"
...
これでandroid のアプリ表示名を切り替えることができるようになりました。
Firebaseの設定ファイル(GoogleService-Info.plist, google -services.json )
本節ではFirebaseの設定ファイルの切り替え方を紹介します。iOS , android どちらもファイルをコピーする方法をとります。
以下のファイル名で各環境のGoogleService-Info.plistを用意します。
devGoogleService-Info.plist
stgGoogleService-Info.plist
prdGoogleService-Info.plist
ファイルが用意できたらios/Firebase/
に置きます。
xcode でTARGETS/Runner/Build Phases
を開き、右上の+
ボタンからNew Run Script Phase
を選択します。
追加できたら以下のスクリプト を設定します。
cp -f ${SRCROOT} /Firebase/${FLAVOR} GoogleService-Info.plist ${SRCROOT} /GoogleService-Info.plist
Phaseの名前はわかりやすいようにCopy GoogleServiceinfo.plist
に変更しています。
最後にGoogleService-Info.plist
の参照をプロジェクトに追加します。
まずios
直下にGoogleService-Info.plist
を置きます。このファイルは後で削除するためファイル名さえあっていれば中身はなんでもOKです。
次にfinderからxcode のRunner直下にGoogleService-Info.plist
をドラッグ&ドロップ します。
ダイアログが表示されたらFinishを押します。これでファイルの参照を作ることができました。
finderからGoogleService-Info.plist
を削除します。削除後にxcode でGoogleService-Info.plist
が赤くなっていれば成功です。
ビルドのたびに差分が発生するのを避けるために.gitignore
に以下を追記します。
.gitignore
**/ios/GoogleService-Info.plist
以上でビルド時にiOS のFirebaseの設定ファイル切り替えられるようになりました。
以下のファイル名で各環境のgoogle-services.json
を用意します。
ファイルが用意できたらandroid/app/src/firebase
に置きます。
次にandroid/app/build.gradle
を開き以下のタスクを定義します。
build.gradle
task copyFirebaseSource(type: Copy) {
from "src/firebase/${dartEnvironmentVariables.FLAVOR}-google-services.json"
into './'
rename { String fileName ->
fileName = "google-services.json"
}
}
tasks.whenTaskAdded {
// ...
it.dependsOn copyFirebaseSource
}
ここでもcopyタスクを利用します。コピーする際にファイル名を変更する必要があるため、renameにファイル名変更の処理を指定しています。
すでにwhenTaskAdded
が定義されている場合、既存のwhenTaskAdded
の最後にit.dependsOn copyFirebaseSource
を追記します。
ビルドのたびに差分が発生するのを避けるために.gitignore
に以下を追記します。
.gitignore
**/android/app/google-services.json
以上でビルド時にandroid のFirebaseの設定ファイル切り替えられるようになりました。
Dart コードからFlavorの値を利用したい場合があります。その際は以下のようにしてアクセスすることができます。
String .fromEnvironment ('FLAVOR' )
dotenv等を利用している場合、この値を使うことで読み込むファイルを切り替えることができます。