Flutter/Riverpodで、ProviderScopeのoverridesを使ってFlavorを伝播する
こんにちは、Flutterでのアプリ開発をメインとしている「Altive株式会社」の村松龍之介(@riscait)です!です。
FlutterとiOSネイティブアプリ開発を仕事を行なっております。
Riverpodでは、Providerはグローバルで定義しますが、 ProviderScope
で囲った箇所でのみProviderが使用になります。
ProviderScope
の overrides
は、テストの際などに Provider をスタブに置き換えたりするのに使うと思います。
ただ、 僕の実装では Flavor を通常の方法で Provider に渡すことができず、ルートで override するしかなかったため、以下の方法を使っています。
今回は、Flavor(開発・本番環境などを分ける用途に使っている)をProviderとしてアプリ全体で使いたい場合を例に、
ProviderScope
の overrides
を使った方を書きます。
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の指定の仕方として、
-
dart-define
を使う -
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です。
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
dependencies:
enum_to_string: ^1.0.14
パッケージ導入例
EnumToStringを使用した場合の書き方は以下の通りです。
少し簡潔になりました。
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 ファイルを指定しています。
void main() => run(flavor: Flavor.development);
※ここでは、 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アプリの開発・運営を承っております。
お気軽にお問い合わせください🫡
Riverpod の実践入門本を公開中です📘
Discussion
flavorProvider.overrideWithProvider
している箇所は、以下のようにoverrideWithValue
で済ませた方が簡単です(これで済ませられない時にoverrideWithProvider
を使います)。また、いくつかあるoverrideしたい場面のうち以下の1つ目に当たる内容ですが、ルートの
ProviderScope
でしかできない故にこれを利用できる場面はけっこう限定的で、overrideの仕組みはテスト用途やScopedProviderをoverrideすることをメイン用途として提供されているはずです。monoさん、詳しく補足いただいてありがとうございます🙏!
教えていただいた内容を追記させていただきたいと思います☺️
どういう実装の場合にoverrideするしかなかったかも教えていただけませんでしょうか?気になりました。
おそらく特段偏った実装では無いと思うのですが、 Appクラスでは
HookWidget
を使っており、MaterialApp
ではinitialRoute:onGenerateRoute: generateRoute
を使っています。僕がパッと他の方法を思いつかなかっただけなので、
override
しないスマートな方法があればぜひ教えていただきたいです🙏☺️