🍍

【Flutter】flutterfire_cli を使って Dart コードのみで環境分けする

2022/09/05に公開約11,400字2件のコメント

eye-catch

はじめに

Flutter × Firebase でアプリ作る場合、誤って本番環境を壊すなどの事故を起こさないために次のように環境分けをします。

環境名 説明
開発環境 (dev) 開発者が開発するときに使用
検証環境 (stg) テスターがテストするときに使用、個人開発だと作らないことが多い
本番環境 (prod) ユーザーが使用

以前は GoogleService-Info.plistgoogle-service.json を Firebase からダウンロードし、各プラットフォーム向けに環境分けを構築していましたが、最近は flutterfire_cli を使えば簡単に Flutter × Firebase のセットアップが出来るようになりました。

https://pub.dev/packages/flutterfire_cli

しかし、flutterfire_cli は環境分け ( Flavor ) に対応していません。そこで本記事では flutterfire_cli を使って環境分けする方法 を紹介します!

また、Firebase プロジェクト情報を GoogleService-Info.plistgoogle-service.json を使わずに Dart コード ( FirebaseOptions ) のみで管理する方法 も合わせて紹介しています。

変更履歴

変更履歴

2022/09/05 初版

2022/09/06 flutterfire_cli の隠しオプションについて追記(miyaken12 さんありがとうございます!)

2022/09/06 main.dartimport as を使うように変更

2022/09/06 GoogleService-Info.plistgoogle-service.json を削除できないかもしれない旨の注意書きを追加

2022/09/07 Android の アプリ名を Flavor に応じて切り替える説明を追加

今回構築する環境分け

  • 開発環境(dev)と本番環境(prod)の 2 系統
  • Android / iOS の 2 つのプラットフォーム
  • GoogleService-Info.plistgoogle-service.json を使わない
  • 環境に応じてパッケージ名やバンドル ID を分ける
  • 環境に応じてアプリ名を分ける

環境

Flutter 3.3.0 • channel stable • https://github.com/flutter/flutter.git
Framework • revision ffccd96b62 (5 days ago) • 2022-08-29 17:28:57 -0700
Engine • revision 5e9e0e0aa8
Tools • Dart 2.18.0 • DevTools 2.15.0

Flutter アプリを新規に作成する

まず、次のコマンドで Flutter アプリを新規に作成します。--org jp.susatthi は組織名なので適宜変更してください。

flutter create flutterfire-sample \
  --org jp.susatthi \
  --project-name flutterfire_sample \
  --platforms ios,android

Flutter アプリ単体の環境分けをする

Firebase と連携をする前に、まずは Flutter アプリ単体の環境分けをします。

Flutter の環境分けの方法

Flutter で環境分けをしたい場合は、起動時のオプションに --flavor <flavor名> をつけることで Android Gradle や Xcode の schemes に任意の <Flavor名> を伝えることが出来ます。

flutter-create-h
flutter create --flavor の説明

しかし Dart のコードに伝えることは出来ないので --dart-define=FLAVOR=<flavor名> と併用して使うことが多いです。

flutter-create-h
flutter create --dart-define の説明

例えば開発環境 (dev) と本番環境 (prod) の VS Code の起動設定は次のようになります。

.vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "mobile-dev",
      "type": "dart",
      "request": "launch",
      "flutterMode": "debug",
      "program": "lib/main.dart",
      "args": [
        "--flavor",
        "dev",
        "--dart-define=FLAVOR=dev",
      ]
    },
    {
      "name": "mobile-prod",
      "type": "dart",
      "request": "launch",
      "flutterMode": "debug",
      "program": "lib/main.dart",
      "args": [
        "--flavor",
        "prod",
        "--dart-define=FLAVOR=prod",
      ]
    }
  ]
}

--dart-define だけで環境分けをしたい場合は次の記事が参考になります。

https://zenn.dev/riscait/articles/separating-environments-in-flutter

Android アプリの環境分けをする

Android アプリの環境分けをしていきます。
起動時の Flavor 指定に応じてパッケージ名とアプリ名を次のように分けてみます。

項目 開発環境 本番環境
パッケージ名 jp.susatthi.flutterfire_sample.dev jp.susatthi.flutterfire_sample
アプリ名 (d)FlutterFire FlutterFire

android/app/build.gradle を次のように修正します。

android/app/build.gradle
    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.debug
        }
    }

+    flavorDimensions 'flavor'
+    productFlavors {
+        dev {
+            dimension 'flavor'
+            applicationIdSuffix '.dev'
+            manifestPlaceholders = [appName: '(d)FlutterFire']
+        }
+        prod {
+            dimension 'flavor'
+            applicationIdSuffix ''
+            manifestPlaceholders = [appName: 'FlutterFire']
+        }
+    }
}

Flavor に応じてアプリ名を切り替えるために、次のように appName を参照するように変更します。

android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.susatthi.flutterfire_sample">
   <application
-        android:label="flutterfire_sample"
+        android:label="${appName}"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">

これで Android アプリの環境分けが終わりました。Android は簡単ですね!

iOS アプリの環境分けをする

iOS アプリの環境分けをしていきます。
起動時の Flavor 指定に応じてバンドル名とアプリ名を次のように分けてみます。

項目 開発環境 本番環境
バンドル名 jp.susatthi.flutterfireSample.dev jp.susatthi.flutterfireSample
アプリ名 (d)FlutterFire FlutterFire

iOS の環境分けは Xcode を使うので、次のコマンドで Xcode を起動します。

iOS プロジェクトを開く
open ios/Runner.xcworkspace

iOS は Flavor に応じた schemes を作成する必要があります。そのために、まずは次の 4 種類の Configurations を作成します。

  • Debug × dev
  • Debug × prod
  • Release × dev
  • Release × prod

さらに、Configurations に紐付けるための xxconfig ファイルを作成します。すでに Debug.xxconfigRelease.xxconfig は用意されているので、dev.xxconfigprod.xxconfig を作成した後にそれらの組み合わせた 4 種類の xxconfig ファイルを作成します。作成後は次のようになります。

xcode

それぞれの中身は次の通りです。

dev.xcconfig
FLUTTER_FLAVOR=dev
PRODUCT_BUNDLE_IDENTIFIER=jp.susatthi.flutterfireSample.dev
DISPLAY_NAME=(d)FlutterFire
FLUTTER_TARGET=lib/main.dart
prod.xcconfig
FLUTTER_FLAVOR=prod
PRODUCT_BUNDLE_IDENTIFIER=jp.susatthi.flutterfireSample
DISPLAY_NAME=FlutterFire
FLUTTER_TARGET=lib/main.dart
Debug-dev.xcconfig
#include "Debug.xcconfig"
#include "dev.xcconfig"
Debug-prod.xcconfig
#include "Debug.xcconfig"
#include "prod.xcconfig"
Release-dev.xcconfig
#include "Release.xcconfig"
#include "dev.xcconfig"
Release-prod.xcconfig
#include "Release.xcconfig"
#include "prod.xcconfig"

上で作成した xxconfig ファイルを使って次のように Configurations を作成します。

xcode
Project Runner > Info > Configurations

次に Configurations を使って以下のように 2 つの schemes を作成します。

dev prod
xcode xcode

Flavor に応じてバンドルIDを切り替えるために、ビルド設定の Product Bundle Identifier に、次のように PRODUCT_BUNDLE_IDENTIFIER を参照するように変更します。

xcode
Targets Runner > Build Settings

Flavor に応じてアプリ名を切り替えるために、次のように DISPLAY_NAME を参照するように変更します。

xcode
Targets Runner > Info

これで iOS アプリの環境分けが終わりました。

Firebase プロジェクトを作成する

flutterfire_cli を使って開発環境と本番環境の Firebase プロジェクトを作成していきます。

https://pub.dev/packages/flutterfire_cli

次のコマンドを実行して flutterfire_cli をインストールしておきます。

dart pub global activate flutterfire_cli

開発環境の Firebase プロジェクトを作成する

まずは次のコマンドで開発環境の Firebase プロジェクトを作成します。各オプションの説明は flutterfire configure -h で確認してみてください。

flutterfire configure \
  --out=lib/firebase_options_dev.dart \
  --platforms=android,ios \
  --ios-bundle-id=jp.susatthi.flutterfireSample.dev \
  --android-package-name=jp.susatthi.flutterfire_sample.dev

上記のコマンドを実行すると一番最初に既存プロジェクトを選択しますが、今回新たにプロジェクトを作成したいので <create a new project> を選択して「flutterfire-sample1-dev」という名前にします。

途中で android/build.gradle & android/app/build.gradle を更新するか?と聞かれるので yes と答えておきます。

flutterfire

これで開発環境の Firebase プロジェクトが作成できました。

本番環境の Firebase プロジェクトを作成する

開発環境と同様に本番環境の Firebase プロジェクト「flutterfire-sample1-prod」を作成します。

flutterfire configure \
  --out=lib/firebase_options_prod.dart \
  --platforms=android,ios \
  --ios-bundle-id=jp.susatthi.flutterfireSample \
  --android-package-name=jp.susatthi.flutterfire_sample

上記のコマンドを実行すると ios/firebase_app_id_file.jsonandroid/app/google-services.json を上書きするか聞かれますが、あとで削除するので yes と答えておきます。

flutterfire

これで本番環境の Firebase プロジェクトが作成できました。

Firebase プロジェクトの設定変更

今回動作確認のために匿名認証のサインインを実装しますので、開発環境と本番環境の Firebase プロジェクトのコンソールの Authentication を開いて、匿名認証を有効化しておきます。

firebase

Flutter アプリと Firebase を連携させる

Flutter アプリと Firebase と連携させて、デフォルトのカウントアップアプリを起動したときに匿名認証でサインインする機能を実装してみます。

Firebase のパッケージをインストールする

pubspec.yaml を修正して firebase_core と firebase_auth をインストールします。忘れずに flutter pub get をしましょう。

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
+  firebase_core: ^1.21.1
+  firebase_auth: ^3.7.0

匿名認証でサインインする

アプリ起動時に未サインインなら匿名認証でサインインする実装をします。--dart-define=FLAVOR=<flavor名> で与えられた flavor名String.fromEnvironment('FLAVOR') で受け取って FirebaseOptions を切り替えています。

今回は flavor を文字列で扱っていますが、enum を使ったほうがよりスッキリ書けると思います。

main.dart
+import 'package:firebase_auth/firebase_auth.dart';
+import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
+import 'package:flutterfire_sample/firebase_options_dev.dart' as dev;
+import 'package:flutterfire_sample/firebase_options_prod.dart' as prod;
+
+const flavor = String.fromEnvironment('FLAVOR');
+
-void main() {
+Future<void> main() async {
+  WidgetsFlutterBinding.ensureInitialized();
+
+  // Flavor に応じた FirebaseOptions を準備する
+  final firebaseOptions = flavor == 'prod'
+      ? prod.DefaultFirebaseOptions.currentPlatform
+      : dev.DefaultFirebaseOptions.currentPlatform;
+
+  // Firebase の初期化
+  await Firebase.initializeApp(
+    options: firebaseOptions,
+  );
+
+  // FirebaseUser を取得する
+  final firebaseUser = await FirebaseAuth.instance.userChanges().first;
+  print('uid = ${firebaseUser?.uid}');
+  if (firebaseUser == null) {
+    // 未サインインなら匿名ユーザーでサインインする
+    final credential = await FirebaseAuth.instance.signInAnonymously();
+    final uid = credential.user!.uid;
+    print('Signed in: uid = $uid');
+  }

  runApp(const MyApp());
}

不要なファイルを削除・修正する

Dart コードで Firebase プロジェクト情報を管理することが出来たので、次のファイルは削除しちゃいます。

  • android/app/google-services.json
  • ios/firebase_app_id_file.json

また、Android ビルド時に google-services.json を探す処理をしている apply plugin: 'com.google.gms.google-services'build.gradle から削除します。

android/app/build.gradle
- // START: FlutterFire Configuration
- apply plugin: 'com.google.gms.google-services'
- // END: FlutterFire Configuration

Xcode で iOS プロジェクトを開いて、GoogleService-Info.plist も削除しちゃいます。

xcode

以上ですべて完了です!Dart コードのみで管理できるようになってかなりスッキリしました!

サンプルコードを公開しています

今回ご紹介したサンプルコードを公開しています。是非参考にしてください!

https://github.com/susatthi/flutterfire-sample

最後に

Flutter 大学という Flutter エンジニアに特化した学習コミュニティに所属しています。オンラインでわいわい議論したり、Flutter の最新情報をゲットしたりできます!こちらのページから参加すると 100 FUT (Flutter Univercity Token) がもらえます!ぜひ Flutter 界隈を盛り上げていきましょう!

https://flutteruniv.com?invite_id=9hsdZHg0qtaMIr6RPRulAaRJfA83

あわせて読みたい

https://codewithandrea.com/articles/flutter-flavors-for-firebase-apps/

https://qiita.com/tokkun5552/items/cd70c65dd06935d8c751

https://zenn.dev/riscait/articles/separating-environments-in-flutter

https://medium.com/flutter-jp/flavor-b952f2d05b5d

Discussion

不要なファイルを削除・修正する
Dart コードで Firebase プロジェクト情報を管理することが出来たので、次のファイルは削除しちゃいます。

良かったら flutterfire configure時に以下オプションを使ってみてください!

--no-apply-gradle-plugins
--no-app-id-json

flutterfire_cli 0.2.4で動作確認できましてgradleの更新やfirebase_app_id_file.jsonファイルの生成は防いでくれます
ドキュメントの整備が落ち着いてなく、隠しオプションがあるっぽいです↓

https://github.com/invertase/flutterfire_cli/issues/14#issuecomment-1103073137

また、Firebase公式がドキュメント化してなく別の方が隠しオプションを含めた記事を最近書いたものもありますので参考になればです!(Flutter公式のFlavor対応のリンク集一覧にもある↓)

https://sebastien-arbogast.com/2022/05/02/multi-environment-flutter-projects-with-flavors

コメントありがとうございます!試してみたら確かに教えて頂いたとおりの動きをしました!隠しオプションについて追記させて頂きました!

ログインするとコメントできます