Flutter + Firebaseの環境別設定（開発、本番） & ビルド手順

### 📌 はじめに
Flutter アプリに Firebase を導入し、**開発環境（staging）と本番環境（production）を分ける** ための手順を備忘も兼ねてまとめました。

#### 内容
- Firebase を **staging（開発用）と production（本番用）に分ける**
- `flutterfire_cli` を使用した Firebase 設定
- `GoogleService-Info` の環境ごとの管理
- 実機ビルド手順
---

## 1️⃣ Firebase プロジェクトの作成
[Firebase](https://console.firebase.google.com/) で **staging**（開発用）と **production**（本番用）を分けるため、**2 つの Firebase プロジェクト** を作成します。

| 環境     | Firebase プロジェクト ID         |
|----------|--------------------------------|
| 開発環境 | `{任意のプロジェクトID}-staging`            |
| 本番環境 | `{任意のプロジェクトID}-production`         |


※APP名-stagingなど分かりやすいプロジェクト名が望ましいです。

---

## 2️⃣ `flutterfire_cli` のインストール
Flutter アプリと Firebase を連携するために、`flutterfire_cli` を使用します。

```sh
dart pub global activate flutterfire_cli
```

パスが通っていない場合は、`.zshrc` / `.bashrc` に以下を追加して適用

```sh
export PATH="$PATH":"$HOME/.pub-cache/bin"
source ~/.zshrc  # または source ~/.bashrc
```

---

## 3️⃣ Firebase 設定ファイルの生成
Firebase の `GoogleService-Info.plist`、`google-services.json`、`firebase_options.dart`を **環境ごとに生成** し、同時にFirebaseプロジェクトにアプリを登録します。

各設定ファイルの役割は下記です。
| **ファイル**                     | **役割**                         | **使われる場所**                                      | **プラットフォーム** |
|-----------------------------|-----------------------------|-------------------------------------------------|----------------|
| `GoogleService-Info.plist`  | iOS 用 Firebase 設定        | iOS SDK (`firebase_auth`, `firebase_core` など) | **iOS**       |
| `google-services.json`      | Android 用 Firebase 設定    | Android SDK (`firebase_auth`, `firebase_core` など) | **Android**   |
| `firebase_options.dart`     | Flutter アプリの Firebase 設定 | Flutter (`firebase_core`)                         | **Flutter 共通** |



### 📌 Staging 用（開発環境）
```sh
flutterfire configure --project={プロジェクト名}-staging \
  --out=lib/firebase_options_staging.dart \
  --ios-bundle-id={任意のbundleID}.staging \
  --android-package-name={任意のパッケージ名}.staging
```
開発環境用に下記の設定ファイルが作成されます。
```sh
 ios/Runner/GoogleService-Info.plist
 android/app/google-services.json
 lib/firebase_options_staging.dart
```
そのまま続けて本番環境用のコマンドを実行すると`google-services.json`、`GoogleService-Info.plist`が本番環境用に**上書きされてしまう**ので、その前にiOSはstaging用としてリネーム、Androidはディレクトリを作成して移動します。
※`firebase_options.dart`はコマンド実行時に開発環境用としてファイル名を指定しているので問題なし
```sh
mv ios/Runner/GoogleService-Info.plist ios/Runner/GoogleService-Info-staging.plist
mkdir android/app/src/staging
mv android/app/google-services.json android/app/src/staging/google-services.json
```

### 📌 Production 用（本番環境）
```sh
flutterfire configure --project={プロジェクト名}-production \
  --out=lib/firebase_options_production.dart \
  --ios-bundle-id={任意のbundleID} \
  --android-package-name={任意のパッケージ名}
```

本番環境用に下記の設定ファイルが作成されます。
```sh
 ios/Runner/GoogleService-Info.plist
 android/app/google-services.json
 lib/firebase_options_production.dart
```

iOSはproduction用としてリネーム、Androidはディレクトリを作成して移動します

```sh
mv ios/Runner/GoogleService-Info.plist ios/Runner/GoogleService-Info-production.plist
mkdir android/app/src/production
mv android/app/google-services.json android/app/src/production/google-services.json
```

**Tips:**
設定ファイルを別環境のもので上書きしてしまった場合でも、Firebaseコンソール上から直接ダウンロードすることが可能です。または、もう一度コマンドを実行すれば再生成されます。

1\.[Firebaseコンソール](https://console.firebase.google.com/) にアクセス。
2\.Firebase にログインし、対象のプロジェクトを開く。
3\.左側のナビゲーションメニューから「⚙️ プロジェクトの設定」をクリック。
4\.「アプリの設定」セクションに移動。
5\.該当のアプリを選択。
6\.「GoogleService-Info.plist をダウンロード」ボタンをクリック。

---

## 4️⃣ iOSの環境設定管理
iOS では `GoogleService-Info.plist` を環境ごとに適用する必要があります。

### 📌 `Info.plist` に以下を追加
Xcodeを起動し、Info.plistを開くか、XMLに直接記載します。

Xcode を開く
``` sh
open ios/Runner.xcworkspace
```
Info.plistのCFBundleDisplayNameについて、アプリ名も環境別に分けるように修正します。
XMLに直接記載する場合は下記を記載
```xml
<key>CFBundleDisplayName</key>
<string>$(DISPLAY_NAME)</string>
```

### 📌 デバッグとリリースでアプリを分けるための設定
ios/Flutter/Debug.xcconfig と ios/Flutter/Release.xcconfig ファイルを編集または作成します。

```Debug.xcconfig:
#include "Generated.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER={任意のbundleID}.staging
DISPLAY_NAME={アプリ名}
```
```Release.xcconfig:
#include "Generated.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER={任意のbundleID}
DISPLAY_NAME={アプリ名 STG}
```

「Target」＞「Runner」 > 「Build Settings」タブで「Product Bundle Identifier」を「$(PRODUCT_BUNDLE_IDENTIFIER)」に設定
![スクリーンショット 2025-03-29 10.47.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581663/611bc483-0662-4850-8af9-d51ebbfa86d5.png)

その他、com.example.xxxxの箇所を設定するbundleIDで置換します。

### 📌 Xcodeのビルドスクリプトを追加
stagingでは「GoogleService-Info-staging.plist」Productionでは「GoogleService-Info-production.plist」が使われるように設定をしていきます。

1.Xcodeを開く（ios/Runner.xcworkspace）
2.Runnerプロジェクトを選択
3.TARGETS > Runner > Build Phases を選択
4.「Run Script」に下記を追記
```xml
#!/bin/sh

# デバッグ出力を有効化
set -x

echo "Starting Firebase configuration script..."

# 初期設定ファイルの作成（スクリプトの最初で実行）
cp "${PROJECT_DIR}/Runner/GoogleService-Info-staging.plist" "${PROJECT_DIR}/Runner/GoogleService-Info.plist"

# PRODUCTION の値を .xcconfig から取得
PRODUCTION_ENV=$(echo "$DART_DEFINES" | base64 -d | grep -E '^PRODUCTION=' | cut -d '=' -f2)
echo "Found PRODUCTION_ENV value: ${PRODUCTION_ENV}"

# デバッグ用の出力
echo "Raw DART_DEFINES: $DART_DEFINES"
echo "Decoded DART_DEFINES: $(echo "$DART_DEFINES" | base64 -d)"

# 値が空の場合のデフォルト値設定
if [ -z "$PRODUCTION_ENV" ]; then
    PRODUCTION_ENV="false"
    echo "PRODUCTION_ENV is empty, using default value: false"
fi

# 環境に応じて適切な設定ファイルをコピー
if [ "$PRODUCTION_ENV" == "true" ]; then
    echo "Using Production Firebase configuration"
    cp "${PROJECT_DIR}/Runner/GoogleService-Info-production.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
else
    echo "Using Staging Firebase configuration"
    cp "${PROJECT_DIR}/Runner/GoogleService-Info-staging.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
fi

# コピー結果の確認
if [ ! -f "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist" ]; then
    echo "Error: Failed to copy GoogleService-Info.plist"
    exit 1
fi

echo "Firebase configuration script completed successfully"
```

---

## **5️⃣ Androidの環境設定管理**
Android では `google-services.json` を環境ごとに適用するため、フレーバー設定を行います。

### 📌 **`android/app/build.gradle` を編集**


androidの下にフレーバー設定を追加します。
```gradle

android {
    flavorDimensions "default"
    productFlavors {
        staging {
            applicationId "{任意のパッケージ名}.staging"
            // 必要に応じて他の設定も追加
            resValue "string", "app_name", "{アプリ名} STG"
        }
        production {
            applicationId "{任意のパッケージ名}"
            // 必要に応じて他の設定も追加
            resValue "string", "app_name", "{アプリ名}"
        }
    }
}

```

## **6️⃣ Firebase の初期化**
Firebase関連の必要なパッケージを追加します。
```pubspec.yml
dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.27.1
```
Firebase の環境を firebase_options_staging.dart(開発環境用)と
firebase_options_production.dart(本番環境用)で切り替えられるように設定します。

📌 main.dart
``` dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options_staging.dart' as staging;
import 'firebase_options_production.dart' as prod;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

    // 🔹 環境（開発・本番）を判定
  const isProduction = bool.fromEnvironment('PRODUCTION', defaultValue: false);

  // Firebase の初期化
  await Firebase.initializeApp(
    options: isProduction
        ? prod.DefaultFirebaseOptions.currentPlatform
        : staging.DefaultFirebaseOptions.currentPlatform,
  );


  runApp(MyApp());
}
```

## 7️⃣ 実機ビルドの準備
### 📌 iPhone 実機を認識
Xcodeでプロジェクトを開くか下記コマンドを実行
```sh
open ios/Runner.xcworkspace
```
1. `Runner` を選択 → `Signing & Capabilities` タブ
2. `Team` を設定（Apple ID を登録）
3. **開発用プロビジョニングプロファイル** を設定

### 📌 Android 実機を認識
1. 端末のUSBデバッグ を有効にする
2. 実機の 設定 → 端末情報 → ビルド番号を7回タップ（開発者オプションを有効化）
3. 開発者オプション から USBデバッグ を有効にする

---

## **8️⃣ Debug で実機ビルド**
端末に開発アプリをインストールするためのコマンドは下記です。
### 🔹 iOS 
開発環境（Staging）
```sh
flutter run --dart-define=PRODUCTION=false
```
本番環境（Production）
```sh
flutter run --dart-define=PRODUCTION=true
```
※--debugをつけなくてもDebugビルドになります。

ビルド時に下記エラーが出る場合はflutter cleanやclean build folderを試して下さい。
```
Could not build the precompiled application for the device.
Error (Xcode): Build input file cannot be found: 'ios/Runner/GoogleService-Info.plist'.
Did you forget to declare this file as an output of a script phase or custom build rule which produces it?


```

### 🔹 Android 
開発環境（Staging）
```sh
flutter run --flavor staging --dart-define=PRODUCTION=false
```

本番環境（Production）
```sh
flutter run --flavor production --dart-define=PRODUCTION=true
```
---

## **9️⃣ Release ビルド & インストール**
### 🔹 iOS 
実機に Release ビルドをインストール
#### **開発環境（Staging）**

```sh
flutter run --dart-define=PRODUCTION=false --release
```
または
```sh
flutter build ios --dart-define=PRODUCTION=false --release
flutter install
```

#### **本番環境（Production）**
```sh
flutter run --dart-define=PRODUCTION=true --release
```
または
```sh
flutter build ios --dart-define=PRODUCTION=true --release
flutter install
```

#### **TestFlight / App Store にアップロードする場合**
```sh
flutter build ipa --dart-define=PRODUCTION=true --release
```
上記を実行し、作成されたipaを[Transporter](https://apps.apple.com/jp/app/transporter/id1450874784?mt=12)でアップロード。
※ipaファイルは`build/ios/ipa/xxxx.ipa`にあります。

または、Xcode で `Product` → `Archive` を実行し、**TestFlight にアップロード**。


---

### 🔹 **Android**
実機に Release ビルドをインストール
#### **開発環境（Staging）**
```sh
flutter run --release --flavor staging --dart-define=PRODUCTION=false
```
または
```sh
flutter build apk --flavor staging --dart-define=PRODUCTION=false
adb install build/app/outputs/flutter-apk/app-staging-release.apk
```

#### **本番環境（Production）**
```sh
flutter run --release --flavor production --dart-define=PRODUCTION=true
```
または
```sh
flutter build apk --flavor production --dart-define=PRODUCTION=true
adb install build/app/outputs/flutter-apk/app-production-release.apk
```

#### **Play Store にアップロードする場合**
```sh
flutter build appbundle --flavor production --dart-define=PRODUCTION=true --release
```

#### **📌 作成された `aab` ファイルの保存場所**
```sh
android/app/build/outputs/bundle/productionRelease/app-production-release.aab
```

#### **📌 Staging 用の `aab` ファイル（内部テスト用）**
```sh
android/app/build/outputs/bundle/stagingRelease/app-staging-release.aab
```
---

### 補足
AndroidでreleaseビルドをしてGoogle Play Storeに公開する場合はアプリを署名するためのkeystoreファイルと呼ばれるものが必要です。
以下、手順です
```sh
$ keytool -genkey -v -keystore ./key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
```
作成されたkeystoreファイルをandroid/appの配下に置きます。
次にandroidディレクトリ配下にkey.propertiesという設定ファイルを作成します。
```key.properties
storePassword=<keystore作成時のパスワード>
keyPassword=<keystore作成時のパスワード>
keyAlias=key
storeFile=key.jks
```
次にandroid/app/build.gradleを修正します。

```build.gradle
    // 🔹 `key.properties` の読み込みを追加
    def keystorePropertiesFile = rootProject.file("key.properties")
    def keystoreProperties = new Properties()
    if (keystorePropertiesFile.exists()) {
        keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
    }

    // buildTypesの前に入れる
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }

    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.release
        }
    }
```
以上でリリースビルドの設定は完了です。

## コマンドまとめ
## **📌 コマンド一覧**
| **ビルド目的** | **ビルドモード** | **環境** | **Android コマンド** | **iOS コマンド** |
|---------------|---------------|---------|-----------------------------|------------------------------|
| **開発用デバッグ（Staging）** | Debug | Staging | `flutter run --flavor staging --dart-define=PRODUCTION=false` | `flutter run --dart-define=PRODUCTION=false` |
| **本番環境デバッグ（Production）** | Debug | Production | `flutter run --flavor production --dart-define=PRODUCTION=true` | `flutter run --dart-define=PRODUCTION=true` |
| **社内テスト・内部テスト（Staging）** | Release | Staging | `flutter build apk --flavor staging --dart-define=PRODUCTION=false --release` | `flutter build ios --dart-define=PRODUCTION=false` |
| **本番リリース（Production）** | Release | Production | `flutter build apk --flavor production --dart-define=PRODUCTION=true --release` | `flutter build ios --dart-define=PRODUCTION=true` |
| **Google Play 内部テスト用（Staging）** | Release | Staging | `flutter build appbundle --flavor staging --dart-define=PRODUCTION=false --release` | N/A |
| **Google Play 本番公開用（Production）** | Release | Production | `flutter build appbundle --flavor production --dart-define=PRODUCTION=true --release` | N/A |
| **App Store / TestFlight アップロード（Staging）** | Release | Staging | N/A | `flutter build ipa --dart-define=PRODUCTION=false --release` |
| **App Store / TestFlight アップロード（Production）** | Release | Production | N/A | `flutter build ipa --dart-define=PRODUCTION=true --release` |

# 補足

### 📌 1️⃣ Debug（デバッグ）と Release（リリース）の違い
これは **Flutter のビルドモード** であり、**アプリの最適化レベルやデバッグ機能の有無** を決めます。

| **モード** | **目的** | **特徴** | **主な用途** |
|-----------|---------|---------|-------------|
| **Debug** | 開発・デバッグ用 | - ホットリロードが可能<br>- `assert()` が動作<br>- 開発ツール（DevTools）が利用可能<br>- 最適化されていないので動作が遅い | - 開発時の動作確認<br>- デバッグ作業 |
| **Release** | 本番リリース用 | - 最適化されており動作が速い<br>- `assert()` は無効<br>- デバッグ情報なし（クラッシュログが少なくなる）<br>- アプリサイズが小さくなる | - 本番環境のビルド<br>- Google Play / App Store にアップロード |

📌 **ポイント**
- `flutter run` のデフォルトは **Debug モード**。
- `flutter build apk / ios / appbundle` のデフォルトは **Release モード**。
- **`--release` を付けると Release ビルドになる**。

---

### 📌 2️⃣ Staging（開発環境）と Production（本番環境）の違い
これは **アプリが接続する Firebase や API サーバーの違い** です。

| **環境** | **目的** | **特徴** | **主な用途** |
|-----------|---------|---------|-------------|
| **Staging** | 開発・テスト用 | - Firebase / API も開発用の環境を使用<br>- 開発チームやテスターが使用<br>- Google Play 内部テストや TestFlight に配布可能 | - 社内テスト・QAテスト<br>- ストア公開前の最終チェック |
| **Production** | 本番リリース用 | - 一般ユーザーが使用する環境<br>- Firebase / API も本番用を使用<br>- 本番リリース版 | - ユーザーに提供するアプリ<br>- Google Play / App Store への公開 |

📌 **ポイント**
- **Staging は開発チームやテスター向けの環境**（本番と同じ設定で動作確認）。
- **Production は一般ユーザー向けの環境**（本番用 Firebase / API を使用）。
- **Staging と Production で Firebase や API のエンドポイントを切り替えることが重要**。

---

### 📌 3️⃣ Debug / Release × Staging / Production の組み合わせ
Flutter では **「ビルドモード（Debug / Release）」と「環境（Staging / Production）」を組み合わせて使う** のが一般的です。

| **目的** | **ビルドモード** | **環境** | **用途** |
|---------|------------|----------|----------|
| **開発中にデバッグ** | Debug | Staging | ホットリロードしながら開発 |
| **本番リリース前のテスト** | Release | Staging | ストアにアップロードする前の動作確認 |
| **ユーザー向け本番リリース** | Release | Production | Google Play / App Store に公開 |

📌 **例**
- **開発時**：`flutter run --flavor staging --dart-define=PRODUCTION=false`
- **本番ビルド時**：`flutter build appbundle --flavor production --dart-define=PRODUCTION=true --release`

---