flutter_flavor + flutter_flavorizr を使って Flutter の Flavor を設定する

Flutter で環境ごとにビルド設定を切り替えるためには、Flavor を設定する必要があります。

[Creating flavors for Flutter - Flutter](https://flutter.dev/docs/deployment/flavors)

ですが調べてみると、Flavor を設定するためには各 OS ごとに手動でそれぞれ設定しなければならないため面倒です。

- [【Flutter】Flavor の設定~build まで(Android 編) - Qiita](https://qiita.com/hiromasa-fun/items/c79c99535f6f1db2a6a9)
- [Flutter で環境ごとにビルド設定を切り替える — iOS 編. モバイルアプリ開発において、環境ごとに設定を変えてビルド・配信することはほぼ必須… | by mono  | Flutter 🇯🇵 | Medium](https://medium.com/flutter-jp/flavor-b952f2d05b5d)

もっと楽して Flavor を設定できないかさらに調べてみたところ、下記のパッケージを使えば実現できるようでした。

- [flutter_flavor](https://pub.dev/packages/flutter_flavor)
- [flutter_flavorizr](https://pub.dev/packages/flutter_flavorizr)

そこで、この 2 つのパッケージを使って Flavor を設定する方法をまとめます。

# 各パッケージのインストール

`pubspec.yaml`に下記の通り記載します。

```yaml:pubspec.yaml
dependencies:
  flutter_flavor: ^1.1.3
  flutter_flavorizr: ^1.0.9
```

その後、`flutter pub get`コマンドを実行します。

# `flutter_flavorizr`

`flutter_flavorizr`とは、各 OS ごとの Flavor の設定を一括管理してくれるパッケージです。そのため、各 OS ごとに手動でそれぞれ設定しなければならない手間を解消してくれます。

## `flutter_flavorizr`を使うための準備

`flutter_flavorizr`を使うためには、事前に下記の 3 つを準備する必要があります。

- [Ruby](https://www.ruby-lang.org/en/documentation/installation/)
- [Gem](https://rubygems.org/pages/download)
- [Xcodeproj](https://github.com/CocoaPods/Xcodeproj)（RubyGems 経由）

Mac の場合、すでに`Ruby`と`Gem`がインストールされていることがありますが、権限の問題上`gem install`が使えません。
そのため`Ruby`と`Gem`をインストールするにあたり、`rbenv`を使ってインストールするのがおすすめです。
インストール方法は下記が参考になりました。

[gem install で permission エラーになった時の対応方法 - Qiita](https://qiita.com/nishina555/items/63ebd4a508a09c481150)

## Flavor の設定

Flavor を設定するには、下記のように pubspec.yaml を編集して Flavor を定義する必要があります。

```yaml:pubspec.yaml
# Flavorの設定
flavorizr:
  app:
    android:
      flavorDimensions: "flavor-type"
    ios:

  flavors:
    local:
      app:
        name: "アプリ名（local）"

      android:
        applicationId: "com.example.testapp.local"

      ios:
        bundleId: "com.example.testapp.local"

    dev:
      app:
        name: "アプリ名（dev）"

      android:
        applicationId: "com.example.testapp.dev"
      ios:
        bundleId: "com.example.testapp.dev"

    prod:
      app:
        name: "アプリ名"

      android:
        applicationId: "com.example.testapp"
      ios:
        bundleId: "com.example.testapp"
```

`flavorizr`という名前の新しいキーを追加し、`app`と`flavors`の 2 つのサブアイテムを定義します。
`app`配下の下で、各 OS 固有のビルド設定を定義できます。
注意点として、`flavorDimensions`は公式だと「required false」と記載がありますが、必ず何かしら記載しないとエラーになります。
特にこだわりがなければ、`flavor-type`と記載しておけば問題ありません。
`flavors`配列の下で、Flavor の名前を定義できます。
今回の例では local と dev、prod を作成します。
Flavor ごとにアプリ名、アプリケーション ID、バンドル ID を指定する必要があります。

## Flavor の設定の反映

Flavor の設定が終了したら、次のコマンドを実行してスクリプトを実行できます。

```
flutter pub run flutter_flavorizr
```

スクリプト完了後、Flavor 用の各種コードが自動生成&追記されます。
また、Flavor ごとに`main-xxx.dart`（`xxx`には Flavor 名）が自動生成されます。

## 動作確認

Android または iOS の実機もしくはエミュレータ等を接続し、次のコマンドを実行してアプリを起動できます。
例えば、Flavor を`local`で起動してみます。

```
flutter run --flavor local -t lib/main-local.dart
```

すると、下記のような画面が起動します。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/e2810fa6-b5bc-009c-3cd1-5202b143e93a.png)

ここで iOS のシミュレータを使って起動しようとすると、下記のエラーが出て起動に失敗する場合があります。

```
Launching lib/main-local.dart on iPhone 12 Pro Max in debug mode...

Running pod install...                                              4.3s
Running Xcode build...
Xcode build done.                                           25.6s
Failed to build iOS app
Error output from Xcode build:
↳
    2021-02-14 08:43:14.336 xcodebuild[4440:55848] [MT] PluginLoading: Required plug-in compatibility UUID 2F1A5FFF-BFEB-4498-B2AC-1296A7454F81 for plug-in at path '~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/Unity4XC.xcplugin' not present in DVTPlugInCompatibilityUUIDs
    2021-02-14 08:43:14.336 xcodebuild[4440:55848] Failed to load plugin at: /Users/Hitoshi/Library/Application Support/Developer/Shared/Xcode/Plug-ins/Unity4XC.xcplugin, skipping.  Reason for failure: *** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]
    ** BUILD FAILED **


Xcode's output:
↳
    diff: /Podfile.lock: No such file or directory
    diff: /Manifest.lock: No such file or directory
    error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.
    note: Using new build system
    note: Building targets in parallel
    note: Planning build
    note: Constructing build description
    warning: Ignoring destination platform iOS and building against target platform iOS Simulator. If this target is intended to build for only iOS Simulator, configure its Supported Platforms build setting to remove iOS. If this target is intended to build for both iOS and iOS Simulator, set its Base SDK build setting to Latest iOS. (in target 'Toast' from project 'Pods')
    warning: The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.4.99. (in target 'Toast' from project 'Pods')
    warning: Ignoring destination platform iOS and building against target platform iOS Simulator. If this target is intended to build for only iOS Simulator, configure its Supported Platforms build setting to remove iOS. If this target is intended to build for both iOS and iOS Simulator, set its Base SDK build setting to Latest iOS. (in target 'flutter_secure_storage' from project 'Pods')
    warning: The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.4.99. (in target 'flutter_secure_storage' from project 'Pods')
    warning: Ignoring destination platform iOS and building against target platform iOS Simulator. If this target is intended to build for only iOS Simulator, configure its Supported Platforms build setting to remove iOS. If this target is intended to build for both iOS and iOS Simulator, set its Base SDK build setting to Latest iOS. (in target 'fluttertoast' from project 'Pods')
    warning: The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.4.99. (in target 'fluttertoast' from project 'Pods')
    warning: Ignoring destination platform iOS and building against target platform iOS Simulator. If this target is intended to build for only iOS Simulator, configure its Supported Platforms build setting to remove iOS. If this target is intended to build for both iOS and iOS Simulator, set its Base SDK build setting to Latest iOS. (in target 'Pods-Runner' from project 'Pods')
    warning: The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.4.99. (in target 'Pods-Runner' from project 'Pods')
    warning: Ignoring destination platform iOS and building against target platform iOS Simulator. If this target is intended to build for only iOS Simulator, configure its Supported Platforms build setting to remove iOS. If this target is intended to build for both iOS and iOS Simulator, set its Base SDK build setting to Latest iOS. (in target 'Runner' from project 'Runner')
    warning: The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.4.99. (in target 'Runner' from project 'Runner')
    warning: Capabilities for Signing & Capabilities may not function correctly because its entitlements use a placeholder team ID. To resolve this, select a development team in the Runner editor. (in target 'Runner' from project 'Runner')
    warning: Ignoring destination platform iOS and building against target platform iOS Simulator. If this target is intended to build for only iOS Simulator, configure its Supported Platforms build setting to remove iOS. If this target is intended to build for both iOS and iOS Simulator, set its Base SDK build setting to Latest iOS. (in target 'Flutter' from project 'Pods')
    warning: The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.4.99. (in target 'Flutter' from project 'Pods')

Could not build the application for the simulator.
Error launching application on iPhone 12 Pro Max.
```

その場合`ios/Runner.xcodeproj`を Xcode 側で開き、`Update to recommended settings`の警告が出ているはずなので解消してください。
詳細は下記サイトが参考になりました。

[Could not build the application for the simulator. Error launching application on iPhone の対応 - Qiita](https://qiita.com/hazime22/items/6c6240e7dd8c2cafdcc0)

# `flutter_flavor`

`flutter_flavor`とは、各 Flavor ごとでバナーの位置や色を変更したり、変数（API の URL 等）を設定できるライブラリです。

## Flavor ごとにリソースを切り替える

例えば local 環境の設定例を下記に示します。

```dart:main-local.dart
void main() {
  FlavorConfig(
      name: "LOCAL",
      color: Colors.red,
      location: BannerLocation.bottomStart,
      variables: {
        "baseUrl": "https://local-testapp.com/api",
      });
  F.appFlavor = Flavor.LOCAL;
  runApp(App());
}
```

`name`、`color`、`location`はそれぞれバナーに表示するラベル名、バナーの色、バナーの位置です。
`variables`はアプリ内部で使えるグローバル変数です。
ここに API の URL を環境に合わせて設定します。
例えば`baseUrl`を設定することで、下記のように呼び出して使用できます。

```dart
// Flavorによって値が変化する
var baseUrl = FlavorConfig.instance.variables["baseUrl"];
```

また、先程設定したバナーを表示するには`FlavorBanner`を使用します。

```dart:app.dart
class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return (
      child: MaterialApp(
        title: F.title,
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(),
      ),
    );
  }
}
```

`FlavorBanner`を親 ウィジェット、`MaterialApp`を子 ウィジェット として設定します。
ここまで出来たら再度ビルドしてアプリを起動すると、下記のような画面が起動します。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93963/ecb5f2e0-0ac6-05e9-37e6-12b7b60d3655.png)

# まとめ

flutter_flavor + flutter_flavorizr を使って Flutter の Flavor を設定しました。
これらのパッケージを使うことで、各 OS ごとに手動でそれぞれ設定する手間が省けました。
また、`pubspec.yaml`で設定を一括管理できるのもメリットだと感じました。
ただ dart コードなどが自動生成されるため、ゴリゴリ実装されている既存プロジェクトへの導入は大変だと感じました。