【Flutter×Firebase】環境分け実装ガイド(dart-define)
はじめに
FlutterアプリケーションでFirebaseを使用する際の環境分け(dev、stg、prod)の実装方法を備忘録として残します
今回は Firebaseのプロジェクト(dev、stg、prod)をそれぞれ用意し、dart-defineを使ってコマンドから違う環境を立ち上げられるようにしていきます。
前提条件
- Flutterプロジェクト作成済み
- Firebaseアカウントの作成済み
全体像
project_root/
├── flavor/ # 環境設定ファイル
│ ├── dev.json # 開発環境設定
│ ├── stg.json # ステージング環境設定
│ └── prod.json # 本番環境設定
│
├── lib/
│ ├── firebase_options/ # Firebase設定
│ │ ├── firebase_options_dev.dart
│ │ ├── firebase_options_stg.dart
│ │ └── firebase_options_prod.dart
│ │
│ ├── core/
│ │ └── constants/
│ │ └── flavor_config.dart # Flavor設定クラス
│ │
│ └── main.dart # メイン処理(環境分岐を含む)
│
├── ios/
│ ├── config/ # iOS用Firebase設定
│ │ ├── dev/GoogleService-Info.plist
│ │ ├── stg/GoogleService-Info.plist
│ │ └── prod/GoogleService-Info.plist
│ │
│ └── scripts/
│ └── extract_dart_defines.sh # dart-defineをiOSに渡すスクリプト
│
├── android/
│ └── app/
│ └── src/
│ └── config/ # Android用Firebase設定
│ ├── dev/google-services.json
│ ├── stg/google-services.json
│ └── prod/google-services.json
│
└── .vscode/
└── launch.json # VS Code実行設定
Firebase CLI
最初にFirebase CLIのインストール。その後アカウントにログイン
npm install -g firebase-tools
firebase login
FlutterFire CLIのインストール
dart pub global activate flutterfire_cli
環境設定ファイルの作成
プロジェクト配下にflavor
ディレクトリを作成し、各環境用の設定ファイルを作成します:
flavor/dev.json
:
{
"FLAVOR": "dev",
"APP_NAME": "Example Dev",
"BUNDLE_ID": "com.example.app.dev"
}
flavor/stg.json
:
{
"FLAVOR": "stg",
"APP_NAME": "Example Stg",
"BUNDLE_ID": "com.example.app.stg"
}
flavor/prod.json
:
{
"FLAVOR": "prod",
"APP_NAME": "Example",
"BUNDLE_ID": "com.example.app"
}
Firebase設定
以下をターミナルでプロジェクト直下ルートで実行
# 開発環境の設定
flutterfire configure \
--project=womadhub-dev \
--out=lib/firebase_options/firebase_options_dev.dart \
--ios-bundle-id=com.womadhub.app.dev \
--android-package-name=com.womadhub.app.dev \
--ios-out=ios/config/dev/GoogleService-Info.plist \
--android-out=android/app/src/config/dev/google-services.json
# ステージング環境の設定
flutterfire configure \
--project=womadhub-stg \
--out=lib/firebase_options/firebase_options_stg.dart \
--ios-bundle-id=com.womadhub.app.stg \
--android-package-name=com.womadhub.app.stg \
--ios-out=ios/config/stg/GoogleService-Info.plist \
--android-out=android/app/src/config/stg/google-services.json
# 本番環境の設定
flutterfire configure \
--project=womadhub \
--out=lib/firebase_options/firebase_options_prod.dart \
--ios-bundle-id=com.womadhub.app \
--android-package-name=com.womadhub.app \
--ios-out=ios/config/prod/GoogleService-Info.plist \
--android-out=android/app/src/config/prod/google-services.json
注意点
Analytics、Crashlytics、Performance Monitoringを使用する場合は、GoogleService-Info.plistとgoogle-services.jsonがそれぞれios・androidディレクトリ内に設定が必要--ios-out=...
,--android-out=...
のコマンドが必要ないです)
Android設定
android/app/build.gradleファイルを以下のように修正して、Flutter定義変数を読み込めるようにします。
android/app/build.gradle
:
def dartDefines = [:];
if (project.hasProperty('dart-defines')) {
dartDefines = dartDefines + project.property('dart-defines')
.split(',')
.collectEntries { entry ->
def pair = new String(entry.decodeBase64(), 'UTF-8').split('=')
pair.length == 2 ? [(pair.first()): pair.last()] : [:]
}
}
// google-services.jsonをコピーするタスク
task copyGoogleServices {
def flavor = dartDefines.FLAVOR ?: 'dev'
doLast {
copy {
from "src/config/${flavor}/google-services.json"
into '.'
}
}
}
android {
namespace "com.example.app"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
// 他の設定...
defaultConfig {
applicationId = dartDefines.BUNDLE_ID ?: 'com.example.app'
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
resValue "string", "app_name", dartDefines.APP_NAME ?: 'ExampleApp'
}
}
// Google Servicesタスクの前にコピータスクを実行
tasks.whenTaskAdded { task ->
if (task.name.startsWith('process') && task.name.endsWith('GoogleServices')) {
task.dependsOn copyGoogleServices
}
}
iOS設定
- スクリプトファイルの作成(
ios/scripts/extract_dart_defines.sh
):
#!/bin/sh
OUTPUT_FILE="${SRCROOT}/Flutter/Dart-Defines.xcconfig"
: > $OUTPUT_FILE
function decode_url() { echo "${*}" | base64 --decode; }
IFS=',' read -r -a define_items <<<"$DART_DEFINES"
for index in "${!define_items[@]}"
do
item=$(decode_url "${define_items[$index]}")
lowercase_item=$(echo "$item" | tr '[:upper:]' '[:lower:]')
if [[ $lowercase_item != flutter* ]]; then
echo "$item" >> "$OUTPUT_FILE"
fi
done
- スクリプトに実行権限を付与:
chmod 755 ios/scripts/extract_dart_defines.sh
- Dart-Defines.xcconfig ファイルを作成:
touch ios/Flutter/Dart-Defines.xcconfig
- 環境別のxcconfigファイルを作成
ios/Flutter/
ディレクトリに以下のファイルを作成します:
dev.xcconfig:
#include "Generated.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER=com.example.app.dev
GOOGLE_CLIENT_ID=*****************************************
DISPLAY_NAME=example Dev
stg.xcconfig:
#include "Generated.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER=com.example.app.stg
GOOGLE_CLIENT_ID=*****************************************
DISPLAY_NAME=example Stg
prod.xcconfig:
#include "Generated.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER=com.example.app
GOOGLE_CLIENT_ID=*****************************************
DISPLAY_NAME=example
-
.gitignore
に追加:
**/ios/Flutter/Dart-Defines.xcconfig
Dartコード(本体コード)の設定
Flavorの設定
アプリケーションでFlavorを利用するための設定クラスを作成します
enum Flavor { dev, stg, prod }
class FlavorConfig {
final Flavor flavor;
final String appName;
final String bundleId;
static FlavorConfig? _instance;
factory FlavorConfig({
required Flavor flavor,
required String appName,
required String bundleId,
}) {
_instance ??= FlavorConfig._internal(flavor, appName, bundleId);
return _instance!;
}
FlavorConfig._internal(this.flavor, this.appName, this.bundleId);
static FlavorConfig get instance {
return _instance!;
}
static bool get isDevelopment => _instance!.flavor == Flavor.dev;
static bool get isStaging => _instance!.flavor == Flavor.stg;
static bool get isProduction => _instance!.flavor == Flavor.prod;
static FirebaseOptions get firebaseOptions => switch (_instance!.flavor) {
Flavor.dev => dev.DefaultFirebaseOptions.currentPlatform,
Flavor.stg => stg.DefaultFirebaseOptions.currentPlatform,
Flavor.prod => prod.DefaultFirebaseOptions.currentPlatform,
};
}
main.dartの設定
main.dart
ファイルで環境設定を読み込み、初期化する処理を追加します
// 環境変数から取得(dart-define-from-fileで渡される)
const flavor = String.fromEnvironment('FLAVOR', defaultValue: 'dev');
const appName = String.fromEnvironment('APP_NAME', defaultValue: 'WomadHub Dev');
const bundleId = String.fromEnvironment('BUNDLE_ID', defaultValue: 'com.womadhub.app.dev');
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Flavor設定の初期化
FlavorConfig(
flavor: _mapStringToFlavor(flavor),
appName: appName,
bundleId: bundleId,
);
// Firebase初期化
await Firebase.initializeApp(
options: FlavorConfig.firebaseOptions,
);
// .envファイル読み込み
await EnvConstants().init();
// 多言語設定の初期化
final savedLocale = await LocalePreferences.getSavedLocale();
if (savedLocale != null) {
LocaleSettings.setLocaleRaw(savedLocale.languageTag);
} else {
LocaleSettings.useDeviceLocale();
}
runApp(
ProviderScope(
child: TranslationProvider(
child: const MyApp(),
),
),
);
}
Flavor _mapStringToFlavor(String flavorString) {
switch (flavorString) {
case 'dev':
return Flavor.dev;
case 'stg':
return Flavor.stg;
case 'prod':
return Flavor.prod;
default:
return Flavor.dev;
}
}
class MyApp extends ConsumerWidget {
const MyApp({super.key});
Widget build(BuildContext context, WidgetRef ref) {
return ScreenUtilInit(
designSize: const Size(390, 844),
minTextAdapt: true,
splitScreenMode: true,
builder: (context, child) {
return MaterialApp.router(
title: FlavorConfig.instance.appName,
debugShowCheckedModeBanner: !FlavorConfig.isProduction,
...
);
},
);
}
}
VS Code設定
{
"version": "0.2.0",
"configurations": [
{
"name": "Development",
"request": "launch",
"type": "dart",
"args": [
"--dart-define-from-file=flavor/dev.json"
]
},
{
"name": "Staging",
"request": "launch",
"type": "dart",
"args": [
"--dart-define-from-file=flavor/stg.json"
]
},
{
"name": "Production",
"request": "launch",
"type": "dart",
"args": [
"--dart-define-from-file=flavor/prod.json"
]
}
]
}
アプリの実行方法
# 開発環境
fvm flutter run --dart-define-from-file=flavor/dev.json
# ステージング環境
fvm flutter run --dart-define-from-file=flavor/stg.json
# 本番環境
fvm flutter run --dart-define-from-file=flavor/prod.json
注意点
環境設定ファイルの管理:
環境設定ファイル(flavor/*.json)はバージョン管理に含めることで、チーム全体で同じ設定を使用できます。ただしAPIキーなどの機密情報は.envファイルで管理し、バージョン管理から除外しましょう。
Firebase設定ファイルの扱い:
Analytics、Crashlytics、Performance Monitoringを使用する場合は、GoogleService-Info.plistとgoogle-services.jsonが必要です。
これらのサービスを使用しない場合は、firebase_options_*.dartの設定だけで十分です。
gitignoreの設定:
以下のファイルはバージョン管理から除外しましょう。
**/ios/Flutter/Dart-Defines.xcconfig
**/android/app/google-services.json
コード内での環境判定:
FlavorConfigクラスを使って環境によって処理を分岐させることができます。
if (FlavorConfig.isDevelopment) {
// 開発環境のみの処理
} else if (FlavorConfig.isProduction) {
// 本番環境のみの処理
}
最後に
Flutterアプリケーションでの環境分け(dev、stg、prod)の実装方法について解説しました。環境ごとに異なる設定(アプリ名、バンドルID、Firebase設定など)を管理することで、開発からリリースまでのワークフローをスムーズに進めることができます。
効率的なFlutterアプリケーション開発に役立てられれば幸いです!
Discussion