[Flutter] --dart-define-from-file で環境依存値を外出しする

# はじめに

https://qiita.com/grhg/items/09daad4a5148cf08c355

で API Key を暫定的にソースコードに含めていました。

```dart
// ...
class RakutenAPI {
  static const String apiKey = '[API Key]'; // 取得したAPIキーをここに入れる
// ...
```

これだと、APIキーをコミットしてしまうリスクがあるため、環境変数化する方法を検討していきます。

# 環境変数化について

アプリケーションの開発では、環境ごと（開発、ステージング、本番など）に異なる値はソースには直接記載せず、外部から取得する仕組みが必要になってきます。
Flutterではビルドしたパッケージを端末にインストールする特性上、以下の２つの方法を使うことができます。

## 環境変数をファイルとしてパッケージに含め、必要に応じて読み込む

`flutter_dotenv` が有名なようです。

https://pub.dev/packages/flutter_dotenv

`flutter_dotenv` は、`.env` ファイルから環境変数を読み込むためのライブラリです。このライブラリを使用すると、アプリケーション内で簡単に設定値を利用できます。

## ビルド時に環境変数を渡してバイナリに含める

`--dart-define` や `--dart-define-from-file` といったオプションをビルドコマンドで指定することで、環境変数を渡すことができます。
渡された環境変数はコード上に埋め込まれ、バイナリに含まれます。

以前は、`--dart-define` で1つずつパラメータを渡す必要がありましたが、Flutter 3.7 からは `--dart-define-from-file` を使ってファイルで渡せるようになりました。

# 方式検討

`flutter_dotenv` と `--dart-define-from-file` を比較し、 `--dart-define-from-file`を採用することにしました。
ここではその詳細を記載します。

## 1. `flutter_dotenv`

`flutter_dotenv` は、Flutterプロジェクトで `.env` ファイルを読み込むためのライブラリです。他のプラットフォームでもよく使われる方法なので、直感的でわかりやすい反面、幾つかデメリットがあります。

### メリット

1. セットアップが容易
   - `.env` ファイルを作成し、ライブラリを追加するだけ

2. 柔軟な環境変数の管理
   - 実行時に動的に環境変数を読み取ることができる

### デメリット

1. コードがライブラリに依存する

   - `flutter_dotenv` ライブラリを導入する必要があり、起動時の `.env` ファイル読み込み処理を入れる必要があります。また、使用時にもライブラリを使って環境変数を取得する必要があります

2. `.env` ファイルが パッケージに plain text で含まれる
   - `.env` ファイルの中身がアプリケーションパッケージにそのまま含まれるため、容易に値を取り出せてしまいます

3. `pubspec.yaml` で asset として明示する必要がある
   - `.env` ファイルを `pubspec.yaml` に記載しなければならず、必ず `.env` ファイルを作成する必要があります。存在しない場合、アプリの起動に失敗します

## 2. `--dart-define-from-file`

Flutterの公式機能である `--dart-define-from-file` を使用する方法です。オプションで指定しないといけないデメリットはあるものの、 `flutter_dotenv` のデメリットを回避することができます。

### メリット

1. ライブラリへの依存が不要
   - オプションを指定するだけなので、ライブラリの導入や明示的な読み込み処理の追加が不要です
   - 利用時も以下のような標準的な方法で値を取得することができます
    ```dart
      static const String apiKey = String.fromEnvironment('RAKUTEN_API_KEY');
    ```

2. 値がバイナリとして含まれる
   - 環境変数はバイナリとしてアプリに埋め込まれるため、plain textと比べて読み取りが困難です
   - ※ ただし、暗号化されるわけではないため、機密情報は含めないように注意が必要

3. 環境ごとの切り替えが容易
   - 使用する `.env` ファイルをビルド時に指定するだけで、環境ごとの設定を簡単に切り替えることができます

### デメリット

1. 設定の手間
   - ビルドコマンドで指定しないといけないため、ビルド時やローカル起動時などは起動設定を入れる必要があります

2. ランタイムでの変更が不可
   - アプリ起動後に環境変数を変更することができません

## 結論

`flutter_dotenv` を使う場合、ライブラリに依存してしまうのと、asset として `.env` ファイルを含める必要があるデメリットが大きいため、`--dart-define-from-file` を使うことにしました。

# 使用例

実際に `--dart-define-from-file` を使用した環境変数の管理方法を実装していきます。

## `.env` ファイルの作成
```env
RAKUTEN_API_KEY=1234567890123456789
```

## 起動設定に追加

### コマンド実行

```bash
# 起動(macos)
flutter run -d macos --dart-define-from-file=.env

# ビルド(macos)
flutter build macos --dart-define-from-file=.env
```

## VSCodeの起動設定を修正

起動設定 (`launch.json`) にオプションを追加します。

```diff_json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Flutter",
            "type": "dart",
            "request": "launch",
-           "program": "lib/main.dart"
+           "program": "lib/main.dart",
+           "args": [
+               "--dart-define-from-file=.env"
+           ]
        }
    ]
}

```

### コード内で環境変数の取得

冒頭の楽天APIを例に修正してみます。

```diff_dart
// ...
class RakutenAPI {
- static const String apiKey = '[API Key]'; // 取得したAPIキーをここに入れる
+ static const String apiKey = String.fromEnvironment('RAKUTEN_API_KEY');
// ...
```

### `.gitignore` に `.env` を追加

誤って環境依存の値を commit してしまわないように除外設定を追加しておきます。

```diff_shell
+ # local environment file
+ .env
```

# まとめ

`--dart-define-from-file` を使うと、ライブラリへの依存を避けつつ、環境依存値を外部に抜き出してみました。

少し調べると、ios ビルド時そのままだと `--dart-define-from-file` で指定した値が読み込まれない[不具合がある](https://zenn.dev/goza/articles/4054edf00e04cd)ようですので、別途見ていきたいと思います。
(iOS での Debug起動時は使えていたので、リリース時の問題か、修正済みなのかも...?)