Flutteアプリ開発において、環境ごとの設定(APIサーバのURLなど)をソースコードのどこに記述するべきかを再検討した。
以前、Flutterアプリにおける環境ごとの設定管理方法に関する考察を書いた際は、「ビルドターゲットと.envのどちらでも良い」という結論だったが、.envで運用していて「ビルドターゲットの方が.envよりも良い」という事がわかったので改めてまとめた。
環境ごとのビルド設定要件
- 異なる環境のバイナリはそれぞれ別アプリとしたい
- 他のビルド設定が結果のバイナリに含まれない
- 1箇所の切り替えだけですべて切り替わる
利用可能な手段
- Flavor: 使い方次第ではあるが、特にapplication IDなど、プラットフォーム固有の設定やプラットフォーム毎に変えたい情報のバリエーションを実現できる。
- ビルドタイプ:ビルド方法を制御する。Flavorと似ているが、Flavorが機能や設定を切り替えるのに対し、ビルドタイプは署名方法や難読化などを切り替える。よくあるreleaseとdebugがイメージしやすい。
- ビルドターゲット: エントリーポイントとなるdartファイルを指定する。例えば、設定値だけが異なるmain_{variant}.dart (main_dev.dartやmain_prod.dartなど) を用意することで、このファイルを指定するだけで設定値を変えたアプリがビルドできる。環境ごとに異なるがプラットフォームで共通な設定は、Flavorよりもこちらで切り替えたほうが効率が良い。
- .env:
--dart-define-from-file
で.envファイルを指定する方法。ビルドターゲットとほぼ同じことができ、Flutter公式ではないがわかりやすい。
結論
「flavor + ビルドターゲット」の方法がベスト。
flavorとビルドターゲットの組み合わせを指定したビルド設定(コマンドオプション設定)をAndroidStuidoおよびVSCodeのビルド設定として作成してソースツリーにコミットしておく。
詳細説明
異なる環境のバイナリはそれぞれ別アプリとしたい
目的
- 同一デバイスに異なる環境のアプリを同時に入れておきたい(わざわざ削除して再インストールとか非効率なので回避したい)
- デバイス上でもデータが混ざらないようにしたい
方法
それぞれに異なるアプリIDを設定することで実現できる。
異なるアプリIDの設定はflavorで実現する。
しかし、flavorで静的に切り替えるものは、プラットフォームごとに記述する必要があり、また記述方法も異なるため、アプリIDなどの最小限のものに留めたい。
なので、flavor以外にもプラットフォーム共通で切り替えたい設定は.envまたはビルドターゲットで実現する。
他のビルド設定が結果のバイナリに含まれない
目的
- リバースエンジニアリングされても、他の環境の情報は漏洩しない
- 実装・ビルドミスで、誤って異なる情報が使われてしまう可能性を大きく減らせる
方法
ビルドターゲットまたは--dart-define-from-file
で指定する.envや.jsonファイルで実現可能。
flutter_dotenv等を使用する方法もあるが、こちらはpubspec.ymlに各.envをassetとして登録する必要があり、異なる環境のビルドを作成するたびに手動または自作スクリプトで切り替えるか、環境ごとの.envファイルを作成してプログラム内で動的に使い分ける必要がある。git管理をしているpubspec.ymlを一時的に書き換えるのは一時的なはずの変更を誤ってコミットするなどの事故になりかねないので回避したい。
1箇所の切り替えだけですべて切り替わる
目的
- 間違っても異なる環境の設定が混ざらないよう、常に設定の一貫性が保たれるようにしたい。
方法
残念ながら、flavorとビルドターゲットを併用すると、ビルドコマンドでそれぞれを個別にパラメータで指定する必要がある。言い換えると、指定を誤れば、意図しないflavorとビルドターゲットの組み合わせでビルドできてしまう。
これを回避するには、AndroidStudoやVS Codeのビルド設定で事前にビルドコマンドのパラメータを登録しておくと良い。これらのビルド設定は、ソースツリーに含めることができるはず。
例
flutter run --flavor dev --target lib/main_dev.dart
flutter run --flavor prod --target lib/main_prod.dart
ビルドターゲット vs .env
.envは人気というか濫用されがちだが、ビルドターゲットの方が良い。
両者とも、任意のファイル名で保存して、ビルドオプションで指定するという点では同じだ。
.envの方が手軽でとっつきやすいが、ビルドターゲットの方がメリットがかなり多く、設定項目が増えたりコードの複雑さが増すほどビルドターゲットでのやり方の方が効率が良い。
実際に.envを使用していたFlutterアプリのプロジェクトでは、設定漏れが発生したり、設定漏れ防止のバリデーション実装が必要だったり、使う時に型を考えたりと、結果的に非効率的であることがわかった。
使い方の説明は以前の記事に書いている。
ビルドターゲット
メリット
- 定義だけで型を特定できる
- 必須/任意を型で指定できる
- 必須項目が足りていないとビルドで検知でき、設定漏れミスを防げる
- 公式で言及されている安心感
デメリット
- .dartファイルなので設定があるファイルが見つけにくい
- ロジックと同じファイルで分かりづらい
- 設定がロジックと混ざってカオスになりかねない
.env
メリット
- 設定がロジックから切り離されている、.dartファイルではないのでわかりやすい。
デメリット
- 非公式。3.7.0以降
--dart-define-from-file
オプションで指定できる様になったが、ドキュメントには書かれていない。また、3.16.0まではbuild.gradleなどから参照できたが現在はできなくしてあるなど、挙動が不明確。 - 設定項目が足りていなくても気づきにくい。
- .envにクレデンシャルを書けば安全とか、コミットすべきではない、という勘違いをする人がいる。詳しくはこちら。