flutter + firebaseで本番環境と開発環境を切り替える

# この記事ですること
* `flutter run`と`flutter run --release`でfirebaseの環境(プロジェクト)を切り替える



# この記事でしないこと
* 別アプリ化
* ステージング環境の構築


# 対象
個人開発者など、複雑な環境構築を必要としないけど、最低限、本番と開発環境を分けたい人

# 複雑に環境を分けたい人は
以下の記事が参考になります。
[flutterで本番/ステージング/開発を切り替える - Qiita](https://qiita.com/ko2ic/items/53f97bb7c28632268b5a)
[Flutterで環境ごとにビルド設定を切り替える — iOS編](https://medium.com/flutter-jp/flavor-b952f2d05b5d)
[【iOS】FlutterでFlavorを使って環境ごとに切り替えてビルドする(debug/stg/prod)](https://qiita.com/skycat_me/items/0a0f5e3982fbfca14444)

# flutterのcreate
defalutでorganaizationがcom.exampleとなっていますが、exampleだとandroid play storeが受け付けてくれませんので、exampleはやめましょう。

> com.exampleは禁止されているため、別のパッケージ名を使用する必要があります。
[アプリケーション ID の設定](https://developer.android.com/studio/build/application-id?hl=ja)より



# firebaseの設定
### プロジェクトの追加
Firebaseプロジェクトから本番用と開発用の２つのプロジェクトを作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/5ecb8a95-b92f-bbba-e2a1-18954837c11e.png)

名前は何でもいいです。規則やルールはありません。私は開発に-devと名付けています。
本番はbase-appで開発はbase-app-devと名付けました。


### アプリの追加
firebaseのプロジェクトの設定からアプリを追加してください。
androidとiosの設定を本番環境(base-app)と開発環境(base-app-dev)の両方に作ってください。

#### iosのアプリの追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/d8f1ddeb-9b4e-0ee1-1913-d2c51b99a838.png)
IOSのバンドルIDはここのBundle Identiferです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/a80a21ef-e46f-9615-d01a-064f5c5b188b.png)

#### androidのアプリの追加
androidのバンドルIDはandroid -> app -> src -> main -> AndroidManifest.xmlの2行目

###### デバッグ用の署名証明書問題
デバッグ用の署名証明書 SHA-1（省略可）はgoogle sign in などで使用しますが、重複できませんので、本番環境側に入力しておきます。
重複すると[このパッケージ名と SHA-1 の組み合わせを持つ OAuth2 クライアントは、別のプロジェクトに既に存在します](https://support.google.com/firebase/answer/6401008?hl=ja)というエラーがでます。  
この手順の`既存のプロジェクトはインポートしたくなく、Invites は使用していない。`の指示に従うと、開発と本番の両方でgoogle sign in が使用できるようになるのらしいですが、私はうまくいきませんでした。（詳細求む）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/292a3cd9-bc09-8554-5fc6-90d406b983ea.png)

firebase sdkの追加をします。ここでは省略します。
設定ファイルをダウンロードしておいてください。本番と開発が混ざらないように注意してください。
この時点でandroidとiosの2設定ファイル × 本番と開発の2環境 = 4ファイル



# flutterのiosの設定(xcode上)
iosの設定は少し面倒です。
targetのrunnerを開いてください。開き方がわからない方は下のgifを見てください。  
一番左のフォルダマークをクリック -> Runnerをクリック -> TERGETSのRunnerをクリック  
![Feb-21-2020 16-29-20.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/31eea010-0cba-5a69-3d43-9468da0ae539.gif)

Build Phasesをクリックします。
+ボタンから`new run script phase`をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/c2444484-9204-70bd-88b6-4de508b9aa7a.png)


一番下に`Run Script`ができますので、リネームします。なんでもいいです。私は`Choose Firebase Environment`と名付けました。

ドラッグアンドドロップで`Run Script`の真下に持ってきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/3b4481bb-16ae-0e2b-e48a-cdf62fca3688.png)

shellに以下を上書きしてください。
echo文いらないですね。必要ない人は削ってください。

```
# Type a script or drag a script file from your workspace to insert its path.
rm -rf "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"

echo "★★★"
echo "-----${CONFIGURATION}-----"
echo "-----${SRCROOT}-----"
echo "-----${BUILT_PRODUCTS_DIR}-----"
echo "-----${PRODUCT_NAME}-----"

if [ "${CONFIGURATION}" = "Debug" ]  ; then
  cp "$SRCROOT/Runner/GoogleService-Info-dev.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
  echo "Development GoogleService-Info copied."
elif [ "${CONFIGURATION}" = "Release" ] ; then
  cp "$SRCROOT/Runner/GoogleService-Info-release.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
  echo "Release GoogleService-Info copied."
fi
```
何をしているのかというと、ビルドの度に`"${CONFIGURATION}"`で開発か本番か判断してビルドされたデータの`GoogleService-Info.plist`を書き換えているのです。
力技でゴリ押してますね。

次にコピー元ファイルを作ります。
`GoogleService-Info-dev.plist`と`GoogleService-Info-release.plist`を作ってRunner配下においてください。  
名前は上記のshellと一致していればなんでもいいでです。
androidと同じようにフォルダつくってもいいかもしれません。私は試したことないですが。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/103f5dcb-20a8-0657-c9ee-0e8de95d423b.png)

xcodeの右側の画面を確認してみてください。FullPathがdownloadフォルダになっていないことを確認してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/5c961f6f-eade-ce74-28bc-b9a4a934fa43.png)


これでfirebaseの環境の切り替え設定が完了しました。

### (補足) info.plistを分けたい場合 
info.plistはアプローチが変わります。
下記のように`$(変数名)`と指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/eb2d522a-a88c-2d64-a559-f52d810b9c04.png)

じゃあ、その変数はどこで設定するかというと、
TARGETのRunner -> Build Swtting -> +ボタン -> Add User-Defined Settingから設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/21428c5e-5e1f-3a5c-ac25-20e78556cc40.png)

debugとreleaseに値を入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/f3ee2c82-e77b-7c63-ea39-f78914fc84c8.png)

# flutterのandroidの設定
androidの設定は簡単です。
以下のスクショと同じになるようにしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/7c4e90cc-a722-e252-0858-c460e180a7c6.png)

### 開発環境
* `android/app/src/debug`フォルダに**開発(base-app-dev)**の`google-services.josn`を格納する

### 本番環境
* `android/app/src/`にreleaseフォルダを作る
* `android/app/src/release/`に**本番(base-app)**の`google-services.josn`を格納する  
フォルダ名は必ず`release`です。階層もdebugと同じ階層です。オリジナルのフォルダ名やオリジナルの階層ではいけません。  
どうやら、debugとreleaseはandroid側のデフォルトの設定のようです。ですから、本番と開発だけなら複雑な設定なしで簡単に切り替えられるみたいです。[詳細はこちら](https://developer.android.com/studio/build/build-variants.html?hl=ja)

### 最後に
実際に`google-services.josn`を開いて5行目の`project_id`などから開発と本番が逆になってないか確かめてください。  
あと、android studioから同期と`flutter clean`コマンドを打つとよいでしょう。


# 本番と開発が分離できているか確かめる
firestoreのデータを取得してみて環境の切り替えができているか確かめます。

### firebase(firestore)の設定
firestoreのデータベースを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/ce6e21d1-bf50-30a3-005a-a2e91bed9684.png)

テストモードで開始します。30日間で期限切れるのがいいですね。 　　
ロケーションには気をつけましょう。変更できませんので。  　
asia-northeast1が東京、2が大阪です。

データを作成します。
checkコレクションにドキュメントidは自動で、"name" = "本番"もしくは"開発"をつくることにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/c72ce7ec-68fa-d4d3-4254-57501326e06c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/acb65aaa-1d98-3622-fc07-7a105dc29d68.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/f28ef1cf-cd8d-e866-ac57-ffbf4f042bff.png)

### flutterの設定
* pubspec.yamlに[cloud_firestore](https://pub.dev/packages/cloud_firestore#-installing-tab-)を記述
* main.dartを以下に書き換え

```dart:main.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}


// 本番かリリースかを判断するには bool.fromEnvironment('dart.vm.product')を使う。
// よりわかりやすくするためにラップして使っている。
bool isRelease() {
  bool _bool;
  bool.fromEnvironment('dart.vm.product') ? _bool = true : _bool = false;
  return _bool;
}


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.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}


class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: Center(
              child: Text(
                  'firestoreの動作確認\n(${isRelease() ? 'リリース' : 'デバック'}モード)'))),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(isRelease() ? "リリースモード" : "デバッグモード",
                style: Theme.of(context).textTheme.title),
            NewWidget(),
          ],
        ),
      ),
    );
  }
}

class NewWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: Firestore.instance.collection('check').snapshots(),
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        }
        switch (snapshot.connectionState) {
          case ConnectionState.waiting:
            return CircularProgressIndicator();
          default:
            return Text('取得した値: ${snapshot.data.documents[0]['name']}');
        }
      },
    );
  }
}

```
うまくいくと画面はこうなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/21a16cef-4ee7-d7a7-a225-c285cac526bd.png)


# 実機でのリリースビルド
リリースビルドはシュミレーターでは動きませんので実機を使用します。
PCにはPixel3が接続されています。

ビルドします。
android: `flutter build apk`
ios: `flutter build ios`

コンソールから`flutter devices`を実行します。このコマンドでインストール先のデバイスを探します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409192/40622fc0-6a96-75ed-fb40-51a19b43d6ac.png)
 
実機にインストール 
`flutter install -d 8`　先頭の１文字だけでもいいのが良いですね。
スクリーンは省略しますが、「本番」という値が取得できます。