💙

[Flutter] 環境設定ファイルを読み取れるようにする

2022/06/26に公開

はじめに

アプリ開発をしていて環境設定ファイル(.env)を用意したくなったことはありませんか?
例えば、google_sign_inを使う時にInfo.plistにREVERSED_CLIENT_IDを追記する必要があります。

ハードコーディングでもいいかも知れませんが、Flavorを分けていたりすると別ファイルを用意してそこから読み取れるようにした方が便利な場面も多いです。
というわけで、Flutterで環境設定ファイルを用意する方法について考えたので共有しようと思います。

ちなみにこの記事の設定を行ったサンプルはここにおいています。

事前準備

Flavorごとに環境設定ファイルを用意しておきます。
今回はFlavorをdev, prodに分け、assets以下に次のようにファイルを用意しました。

assets
└── env
    ├── .env.dev
    └── .env.prod

.env.devファイルの中身は次のような内容です(必ずkeyとvalueを=で繋ぎ、間にスペースは含まないようにしてください)。

GOOGLE_MAP_API_KEY_ANDROID=xxx
GOOGLE_MAP_API_KEY_IOS=xxx
REVERSED_CLIENT_ID=xxx

また、環境設定ファイルはGitで管理すべきではないため、.gitignoreにも追記しておきましょう。

/assets/env

iOS編

iOSの設定を済ますことで、.envファイルの内容を次の箇所で参照できるようになります。

  • Info.plist
  • AppDelegates.swift
  • Flutterツールキット側(.dartファイル内)

処理の概要

次に処理の概要をざっくりと。
アプリビルド時に下記のような処理が自動で行われるように設定していきます。

1. EnvironmentVariables.xcconfig生成

Info.plist内で環境ファイルを参照できるようにするための設定をしていきます。

ios/scripts/generate_env.shを作成し、次の内容にします。

#!/bin/sh

OUTPUT_FILE="${SRCROOT}/Flutter/EnvironmentVariables.xcconfig"

function decode_url() { echo "${*}" | base64 --decode; }

: > $OUTPUT_FILE

IFS=',' read -r -a define_items <<< "$DART_DEFINES"

for index in "${!define_items[@]}"
do
    item=$(decode_url "${define_items[$index]}")
    if [ $(echo $item | grep 'FLAVOR') ] ; then
        value=${item#*=}
        file="${SRCROOT}/../assets/env/.env.${value}"
        while IFS= read -r line || [ -n "$line" ]
        do
            echo $line >> ${OUTPUT_FILE}
        done < "$file"
    fi
done

ios/Runner.xcworkspaceを開き、Debug.xcconfig, Release.xcconfig
#include "EnvironmentVariables.xcconfig"を追記します(この時点ではEnvironmentVariables.xcconfigは生成されていませんのでエラーが出ますが気にしないでください)。

次に、Product->Scheme->EditSchemeを開き、

  1. Build->Pre-actionsを選択
  2. +ボタンからNew Run Script Actionsを選択
  3. ${SRCROOT}/scripts/generate_env.shを記述(Runner指定も忘れずに)

これでアプリのビルド前にEnvironmentVariables.xcconfigが生成されるようになります。

※もしかするとシェルスクリプトに実行権限を付与しておく必要があるかも知れません。

chmod +x ios/scripts/generate_env.sh

この設定によって、例えばgoogle_sign_inの公式ドキュメントには下記の設定方法が書いていますが、

<array>
	<!-- TODO Replace this value: -->
	<!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID -->
	<string>com.googleusercontent.apps.861823949799-vc35...</string>
</array>

それがこのように環境設定ファイルに記述した値(ここではREVERSED_CLIENT_ID)を使用できます。

<array>
	<!-- TODO Replace this value: -->
	<!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID -->
	<string>$(REVERSED_CLIENT_ID)</string>
</array>

2. .env生成

続けて、AppDelegates.swiftやFlutter側で環境ファイルを参照できるようにするための設定をしていきます。

XcodeのBuild Phasesに環境設定ファイルをコピーする処理を追加します。

  1. Runnerを選択
  2. Build Phasesを選択
  3. New Run Script Phasesを押下

内容を次のようにします。
ここではフェーズ名をCopy Environment Variablesとしました。

これにより、アプリビルド時にプロジェクトのルート直下に.envが生成されるようになります。

↓コピー用

\cp -f ${SRCROOT}/../assets/env/.env.${FLAVOR} ${SRCROOT}/../.env

次に、AppDelegates.swiftやFlutter側で読み取れるようにします。
flutter_configという便利なパッケージがあるのでそちらを使わせていただきます。

基本的には公式ドキュメント通り設定するだけなので難しくはないと思いますが、表に明記されていない部分もあるため軽く触れておきます。

Flutter側の設定

  1. パッケージインストール後、main関数内に環境設定ファイル読み込み処理を追記
import 'package:flutter_config/flutter_config.dart';

void main() async {
  // 次の2行を追記する
  WidgetsFlutterBinding.ensureInitialized();
  await FlutterConfig.loadEnvVariables();

  runApp(MyApp());
}

このような呼び出しにより値が取得できます。

FlutterConfig.get('REVERSED_CLIENT_ID')

AppDelegates.swiftの設定

AppDelegates.swiftで読み取る方法について、何故か公式ドキュメントには記載がありませんでしたが、Issueにはその方法が書かれていたので紹介しておきます。

↓該当のIssue
https://github.com/ByneappLLC/flutter_config/issues/15#issuecomment-651231856

AppDelegates.swiftにimport flutter_configを追記し、次の呼び出しにより環境設定ファイルの値を参照することができます。

FlutterConfigPlugin.env(for: "SOME_KEY_FROM_ENV")

例えば、google_maps_flutterの公式ドキュメントには、次のコードの// 1. の方法が記載されていますが、// 2. のように記述することができるようになります。

import UIKit
import Flutter
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // 1. 公式ドキュメント通りの方法(キーをハードコーディングする)
    GMSServices.provideAPIKey("YOUR KEY HERE")
    
    // 2. 環境設定ファイルから読み取ったキーを使う
    GMSServices.provideAPIKey(FlutterConfigPlugin.env(for: "GOOGLE_MAP_API_KEY_IOS"))
    
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Android編

WIP

flutter_config公式のAndroidドキュメントはこちら(https://github.com/ByneappLLC/flutter_config/blob/master/doc/ANDROID.md)。

  • GitHubのコード該当箇所

https://github.com/ymgn9314/flutter_playground/blob/main/android/app/build.gradle#L39-L43

https://github.com/ymgn9314/flutter_playground/blob/main/android/app/build.gradle#L49-L51

  1. android/app/build.gradleの↓のコードがpreBuildタスクのタイミングで呼ばれ、assets/.env.${FLAVOR}がルート直下の.envにコピーされる
task selectEnvironmentVariable(type: Copy) {
    from "../../assets/env/.env.${dartEnvironmentVariables.FLAVOR}"
    into "../../"
    rename { name -> ".env" }
}

ここの追加もお忘れなく。
https://github.com/ymgn9314/flutter_playground/blob/main/android/app/proguard-rules.pro#L1

  1. build.gradleやAndridManifest.xml内でも.envの読み取りはflutter_configに任せてる

Discussion