🌬

Flutter/Riverpodで、ProviderScopeのoverridesを使ってFlavorを伝播する

2020/11/22に公開
4

こんにちは、Flutterでのアプリ開発をメインとしている「Altive株式会社」の村松龍之介(@riscait)です!です。
FlutterとiOSネイティブアプリ開発を仕事を行なっております。

Riverpodでは、Providerはグローバルで定義しますが、 ProviderScope で囲った箇所でのみProviderが使用になります。

ProviderScopeoverrides は、テストの際などに Provider をスタブに置き換えたりするのに使うと思います。
ただ、 僕の実装では Flavor を通常の方法で Provider に渡すことができず、ルートで override するしかなかったため、以下の方法を使っています。

今回は、Flavor(開発・本番環境などを分ける用途に使っている)をProviderとしてアプリ全体で使いたい場合を例に、
ProviderScopeoverrides を使った方を書きます。

Flavor の定義

今回の例では、開発環境用の development 、テスト環境用の staging 、本番環境用の production
の3種類のFlavorを用意したとします。

enum Flavor {
  development,
  staging,
  production,
}

Provider の定義

Flavor用のProviderを定義します。

定義場所は、 特にこだわりが無ければ、 enum Flavor を定義したのと同じファイルで良いかと思います。

// 初期値を `null` にする場合
final flavorProvider = Provider<Flavor>((ref) => null);

初期値を null にする場合は、型を明示する必要があります

// 初期値を `development` にする場合
final flavorProvider = Provider((ref) => Flavor.development);

Flavorはアプリ内で変更されない=状態を持たない
なので、普通の Provider で良いですね。

アプリ起動時に指定した Flavor を受け取ってセットする

Flavorの指定の仕方として、

  1. dart-define を使う
  2. main.dart ファイルを環境別に用意する

の2通りありますので順に書いていきます。

① dart-define で Flavor を分けている場合

--dart-define を使う場合は、 main.dart ファイルを複数作成する必要がない代わりに、
main関数の中で flavor を判別する必要があります。

# ビルドモード:debug, Flavor:development の場合の起動コマンド
flutter run --debug --flavor development --dart-define=FLAVOR=development

--dart-define で Flavor を指定します。

main.dart ファイルで Flavor を判定

const String.fromEnvironment('FLAVOR') で、--dart-defineで指定したFlavor文字列を取得できます。
以下のように、 if文やswitch-case で判定して Flavor を返せばOKです。

main.dart
void main() {
  final flavor = () {
    switch (const String.fromEnvironment('FLAVOR')) {
      case 'development':
        return Flavor.development;
      case 'staging':
        return Flavor.staging;
      case 'production':
        return Flavor.production;
    }
    throw AssertionError('Invalid Flavor');
  }();

  runApp(
    ProviderScope(
      overrides: [
        // Flavor Provider にセット
        flavorProvider.overrideWithValue(flavor),
      ],
      child: const MyApp(),
    ),
  );
}

EnumToStringパッケージを使用した例

enum_to_string パッケージを使用すると、 enum から文字列を取得したり、文字列から enum を取得したりが簡単になります。
enum_to_string - pub.dev

pubspec.yaml
dependencies:
  enum_to_string: ^1.0.14

パッケージ導入例

EnumToStringを使用した場合の書き方は以下の通りです。
少し簡潔になりました。

main.dart
void main() {
  final flavor = EnumToString.fromString(
    Flavor.values,
    const String.fromEnvironment('FLAVOR'),
  );

  runApp(
    ProviderScope(
      overrides: [
        // ここで実際の Flavor をセットする
        flavorProvider.overrideWithValue(flavor),
      ],
      child: const MyApp(),
    ),
  );
}

EnumToString を使えば、 enum ごとに自分で extension を書く必要が無くて楽ですね!

② main ファイルで Flavor を分けている場合

# ビルドモード:debug, Flavor:development の場合の起動コマンド
flutter run --debug --flavor development --target lib/main_development.dart

--target で main ファイルを指定しています。

main_development.dart
void main() => run(flavor: Flavor.development);

※ここでは、 run.dart という全環境共通のファイルを別途作っている場合を想定します。

run.dart
Future<void> run({ Flavor flavor}) async {
  return runApp(
    ProviderScope(
      overrides: [
        // ここで実際の Flavor をセットする
        flavorProvider.overrideWithValue(flavor),
      ],
      child: const MyApp(),
    ),
  );
}

overrides を使って、 main ファイルから受け取った Flavor を Provider にセットしています。

参考リンク

Flutterで環境ごとにビルド設定を切り替える — iOS編

ご閲覧ありがとうございました!

Twitterでも、主に Flutter, Firebase、iOS/Swift について呟いております。
フォローしていただければ嬉しいです☺️ → 🐦村松龍之介@riscait

宣伝

Altive株式会社では、Flutterアプリの開発・運営を承っております。
お気軽にお問い合わせください🫡
https://altive.co.jp/contact


Riverpod の実践入門本を公開中です📘
https://zenn.dev/riscait/books/flutter-riverpod-practical-introduction

GitHubで編集を提案
Altiveエンジニアリングブログ

Discussion

monomono

flavorProvider.overrideWithProviderしている箇所は、以下のようにoverrideWithValueで済ませた方が簡単です(これで済ませられない時にoverrideWithProviderを使います)。

    ProviderScope(
      overrides: [
        flavorProvider.overrideWithValue(flavor),
      ],

また、いくつかあるoverrideしたい場面のうち以下の1つ目に当たる内容ですが、ルートのProviderScopeでしかできない故にこれを利用できる場面はけっこう限定的で、overrideの仕組みはテスト用途やScopedProviderをoverrideすることをメイン用途として提供されているはずです。

村松龍之介村松龍之介

monoさん、詳しく補足いただいてありがとうございます🙏!
教えていただいた内容を追記させていただきたいと思います☺️

tetsufetetsufe

ただ、 僕の実装では Flavor を通常の方法で Provider に渡すことができず、ルートで override するしかなかったため、以下の方法を使っています。

どういう実装の場合にoverrideするしかなかったかも教えていただけませんでしょうか?気になりました。

村松龍之介村松龍之介

おそらく特段偏った実装では無いと思うのですが、 Appクラスでは HookWidget を使っており、 MaterialApp では initialRoute:onGenerateRoute: generateRouteを使っています。
僕がパッと他の方法を思いつかなかっただけなので、 override しないスマートな方法があればぜひ教えていただきたいです🙏☺️