1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Flutter】--dart-define でFirebaseの環境を分ける

Last updated at Posted at 2021-08-28

##はじめに
Firebaseをバックエンドとして、FlutterでAndroid, iOSアプリを開発しているとflutter run する時に環境を分けたい。(Firebaseプロジェクトの接続先を変えたい)
すでにある他の記事を参考に達成できたので、記憶が新しいうちに自分用にまとめておく。
--flavorを使った分け方もありますが、今回は --dart-define=XXX=YYYとビルドモードだけを使って環境を分けます。(とりあえずiOSのみ。また後日Androidもまとめたい)

####前提

  • Flutterのバージョン: 2.2.3
  • FlutterとFirebaseの繋ぎ方を知っている(Firestoreからデータを取得したことがある)

##本編
まずは、Flutterアプリを作成
(今回はbase_ogen3というFlutterアプリを作成しました。)

$ flutter create base_ogen3

###Firebaseにプロジェクトを用意
まずは、Firebaseで分けたい環境の数だけプロジェクトを作成する。
今回は3つ作ります
(プロジェクト名はなんでもよい。ただし、語尾を「-dev」のようにすると環境がわかりやすい)

  • base-ogen3-dev
  • base-ogen3-stg
  • base-ogen3-prod

firebase projects.png

次にそれぞれにiOSアプリを追加します。
追加時、Bundle Id はそれぞれ別アプリ化したいので以下を用意しました。

  • base-ogen3-dev → com.ogen3.base-ogen3.dev
  • base-ogen3-stg → com.ogen3.base-ogen3.stg
  • base-ogen3-prod → com.ogen3.base-ogen3.prod

そして、それぞれのGoogleService-Info.plistをダウンロードしてきて、ファイル名をそれぞれ以下に変更します。

  • base-ogen3-dev → GoogleService-Info-prod.plist
  • base-ogen3-stg → GoogleService-Info-stg.plist
  • base-ogen3-prod → GoogleService-Info-dev.plist

ここまで用意できたら、Firebaseから一旦離れ、Xcodeに移ります。

###Xcode側の設定をする

まずは、Xcodeを開く(画像はAndroid Studioから開いています)
open xcode.png

Firebaseという名前のgroupを作成し、続いてAdd Files to "Runner"...を選択
create firebase group.png

先ほど、ダウンロード&改名しておいたGoogleService-Info(-dev, -stg, -prod).plist たちを追加します。
Add to targets にチェックも忘れずに
add google service info.png

次に、Firebase配下ではなく、Runner配下にGoogleService-Info.plistを追加する。
これは後ほどの設定で、GoogleService-Info(-dev, -stg, -prod).plist のどれかに置き換わるので、ファイル名さえGoogleService-Info.plistになっていれば中身がなくてもよいみたいですが、今回はbase-ogen3-dev(Firebaseプロジェクト)のものを新しくダウンロードして、改名せず配置しました。
target google service.png

ここからはGoogleService-Info.plist を環境に合わせて変更できるようにしていきます。

まずは、base_ogen3/ios配下に replace_google_service_infoというファイルを作成

$ touch ios/replace_google_service_info.sh

また、ビルド時にパーミッションの問題が発生することがあるので、以下しておく。

$ chmod 755 ios/replace_google_service_info.sh

中身を以下のようにする。
DEFINE_BUILD_ENVの部分は --dart-defineで渡す値のキーに相当
(例: --dart-define=DEFINE_BUILD_ENV=dev)

base_ogen3/ios/replace_google_service_info.sh
#! /bin/bash

if [[ $DEFINE_BUILD_ENV == *"dev"* ]]; then
  cp $PRODUCT_NAME/Firebase/GoogleService-Info-dev.plist $PRODUCT_NAME/GoogleService-Info.plist
elif [[ $DEFINE_BUILD_ENV == *"stg"* ]]; then
  cp $PRODUCT_NAME/Firebase/GoogleService-Info-stg.plist $PRODUCT_NAME/GoogleService-Info.plist
elif [[ $DEFINE_BUILD_ENV == *"prod"* ]]; then
  cp $PRODUCT_NAME/Firebase/GoogleService-Info-prod.plist $PRODUCT_NAME/GoogleService-Info.plist
else
  echo "configuration didn't match to Development."
  echo $DEFINE_BUILD_ENV
  exit 1
fi

ファイルを作成したら、Build Phasesタブを開き、ボタンからNew Run Script Phaseを選択
Screen Shot 2021-08-25 at 8.20.05.png

追加されたRun Scriptの Shellのところに./replase_google_service_info.shを記入
また、Output Filesボタンで$SRCROOT/Runner/GoogleService-Info.plistを記入
Screen Shot 2021-08-25 at 8.21.48.png

Run ScriptというタイトルをわかりやすいようにReplace Google Service Infoにリネームして、Copy Bundle Resourcesより上に配置
Screen Shot 2021-08-25 at 8.28.48.png

次は、--dart-difine=XXX=YYYで渡された値をXcode側で使えるようにします。
まずはFlutter配下にNew File...
Screen Shot 2021-08-27 at 8.07.15.png

Configuration Setting Fileを選択して、Next
Screen Shot 2021-08-27 at 8.07.37.png

ファイル名をDartDefineDefaultsとしてCreate
※FolderをFlutterにする
※GroupをFlutterにする
※Targetsにチェックを入れる
※拡張子は作成後に自動で.xcconfigとしてくれる
Screen Shot 2021-08-27 at 8.09.07.png

作成したら、中身を以下にする

DEFINE_BUNDLE_ID_SUFFIX=.dev

これは最終的に、ビルド時に--dart-define=DEFINE_BUNDLE_ID_SUFFIX=XXXXとして実行するが、これが実行時に指定されなかった時(もしくはタイポしてしまった時)などにXcode側でのデフォルト値として使えるようにするために用意しています。

Screen Shot 2021-08-27 at 8.18.48.png

Debug.xccofigを開いて、ファイルの下に以下の2行を追加します。
(この2行の順番は大事なので、DartDefine.xcconfigが下に来るように記述)

Debug.xcconfig
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig".
#include "DartDefineDefaults.xcconfig" <=追加
#include "DartDefine.xcconfig"         <=追加

Screen Shot 2021-08-28 at 13.00.57.png

Release.xcconfigも同様にコード追加
Screen Shot 2021-08-28 at 13.00.45.png

次に、Build Settingsタブを開き、Packagingセクション内のProduct Bundle Identifierのドロップダウンを開きます。
Debug Profile Releaseそれぞれの値を編集で語尾に$(DEFINE_BUNDLE_ID_SUFFIX)を付け加えます。

この設定をすると、バンドルIDの語尾が.devになるはずです。

(余談:参考画像では com.example.baseOgen3.dev となっていますが、これは冒頭に述べた今回用意したBundle ID(com.ogen3.base-ogen3.dev)とは異なっていることに気づかず少しハマりました。 ここはcom.ogen3.base-ogen3.devでした)
Screen Shot 2021-08-28 at 13.06.57.png

続いて、Product > Scheme > Edit Scheme...を選択
Screen Shot 2021-08-27 at 8.23.58.png

サイドメニューのBuildを開いてPre-actionsを選択し、下部にある+を選択してNew Run Script Action
Screen Shot 2021-08-27 at 8.25.11.png

Provide build settings fromの部分をRunnerにして
中身を以下にする
(ただし、ここのコードはFlutterのバージョンによって異なります。参考記事: flutterのdart-defineは2.2以上ではbase64でエンコードされるので対応したスクリプトの紹介

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

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


for index in "${!define_items[@]}"
do
    define_items[$index]=$(entry_decode "${define_items[$index]}");
done

printf "%s\n" "${define_items[@]}"|grep '^DEFINE_' > ${SRCROOT}/Flutter/DartDefine.xcconfig

Screen Shot 2021-08-27 at 8.26.06.png

ここまでで準備が完了しました。あとはFlutterに戻り確認しましょう。

###動作確認

確認の準備をするために以下をpubspec.yamlに追加

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  package_info: ^2.0.2    #<= 追加
  firebase_core: ^1.5.0    #<= 追加
  cloud_firestore: ^2.5.0  #<= 追加

$ flutter clean && flutter pub get

FlutterFireのOverViewを参考にFirebaseをFlutterにセットアップ
main.dartを編集します

####Firestoreのデータを表示するコード追加

main.dart
import 'package:firebase_core/firebase_core.dart'; //追記
import 'package:flutter/material.dart';

void main() async { // async を追記
  WidgetsFlutterBinding.ensureInitialized(); //追記
  await Firebase.initializeApp(); //追記
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //

次にFirebaseからデータを取得するコード追加します。
今回はFlutterFireの One-time Readのサンプルコードをそのまま利用します。
追加する場所はもちろんどこでもいいですが、ここではmain.dartの一番下に追記していきます

main.dart
// この上に _MyHomePageState クラスがあるはず(flutter create で自動で生成されるやつ)
// ここから下に追加
class GetUserName extends StatelessWidget {
  final String documentId;

  GetUserName(this.documentId);

  @override
  Widget build(BuildContext context) {
    CollectionReference users = FirebaseFirestore.instance.collection('users');

    return FutureBuilder<DocumentSnapshot>(
      future: users.doc(documentId).get(),
      builder:
          (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {

        if (snapshot.hasError) {
          return Text("Something went wrong");
        }

        if (snapshot.hasData && !snapshot.data!.exists) {
          return Text("Document does not exist");
        }

        if (snapshot.connectionState == ConnectionState.done) {
          Map<String, dynamic> data = snapshot.data!.data() as Map<String, dynamic>;
          return Text("Full Name: ${data['full_name']} ${data['last_name']}");
        }

        return Text("loading");
      },
    );
  }
}

上記のコードではFirestoreのusersというコレクションから引数で受け取ったdocumentIdでデータを取得して、そのドキュメントのfull_namelast_nameというフィールドのデータを表示するコードになっているので、これに合わせて自分のFirestoreも作成します。

####Firestoreにサンプルデータを作成
こんな感じになると思います。(ドキュメントIDはabcdとしました。)
また下記画像は-devのプロジェクトのものですが、同じように-stg-prodのfirebaseプロジェクトにもデータを用意します。
ただし、full_namelast_nameの値の語尾はそれぞれの環境を表すように.dev.stg.prodとしています。
Screen Shot 2021-08-27 at 9.03.02.png

またFirestoreのセキュリティルールもデータを取得できるように以下に変更しておきます。

allow read, write: if true;

注意:この記述は簡易的にデータの取得を確認するために書いています。本当のサービスなどで利用するには危険だと思われるので、本格的に運用するときは、しっかりとしたセキュリティルールを書くようにしてください。
Screen Shot 2021-08-27 at 9.06.43.png

Flutterに戻り、main.dartで先ほど追加したGetUserNameウィジェットを表示できるよう編集します。(FutureBuilderの部分はFirebaseでなく package_info のコードです。)
参考画像ではGetUserNameの引数が'abcdefg'となっていますが、Firestoreに用意したドキュメントIDは'abcd'だったので、後者にしてください。

main.dart
          // _MyHomePageState内
          // ここから追記
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
          children: <Widget>[
            FutureBuilder<PackageInfo>(
                future: PackageInfo.fromPlatform(),
                builder: (context, value) {
                  if (!value.hasData) {
                    return Container();
                  }
                  return Text(
                    value.data?.packageName ?? 'null value',
                    style: Theme.of(context).textTheme.headline6,
                  );
                }
            ),
            const SizedBox(height: 50),  
            GetUserName('abcdefg'),
            const SizedBox(height: 50),
            Text(
              'You have pushed the button this many times:',
            ),
            // ここまでが追記
            Text(

Xcodeを開き、Runner > PROJECT Runner を選択し、 Infoタブにて iOS Deployment Target11.0にする
Screen Shot 2021-08-28 at 11.55.49.png

念の為Podfile内の platform :ios, '11.0'にしておく

Screen Shot 2021-08-28 at 11.56.30.png

これで準備は全て完了です。実行しましょう。

###実行
--dart-defineの値やビルドモードは好きな組み合わせで試してください)

$ flutter run --debug --dart-define=DEFINE_BUNDLE_ID_SUFFIX=.dev --dart-define=DEFINE_BUILD_ENV=dev

画面に--dart-defineで指定した環境(Firebaseプロジェクト)からデータを取得できているのがわかると思います。

Screen Shot 2021-08-28 at 15.48.43.png

以上です。

##まとめ & 感想

この環境分けに取り組み始めた時はとてもややこしく感じたが、やろうとしていることを大きく捉えると以下の2工程かなと思い、これを意識しながらやるとなんとかできた。

  1. FlutterからXcode側にdart-defineで値を渡す。
  2. Xcode側で受け取った値を使えるようにし、それらを使ってBundle IDやらGoogleService-Info.plitやらを変えていく

個人的には --flavor を使った環境分けよりXcodeでのScheme設定設定が少なくていいなと思った。

最後までお読みいただきありがとうございました。

##参考記事
Flutterで環境ごとにビルド設定を切り替える — iOS編
Flutter 1.17 — no more Flavors, no more iOS Schemas. Command argument that changes everything
dart-defineでFlutterアプリのFirebase開発環境と本番環境を使い分ける iOS編
flutterのdart-defineは2.2以上ではbase64でエンコードされるので対応したスクリプトの紹介
FlutterからXcodeへ環境変数を渡す
Flutterで本番/開発 別に切り分けてアプリを入れる

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?