【Flutter】非同期で作成が必要なxxxインスタンスをriverpodで保持し、使い回したい

2023/01/18に公開

やりたかったこと

例えば、 shared_preferences や、agora_rtc_engine パッケージ。
下記の通り、インスタンス生成が非同期になっています。

shared_preferences インスタンス生成
final prefs = await SharedPreferences.getInstance();
agora_rtc_engine インスタンス生成
_engine = createAgoraRtcEngine();
await _engine.initialize(const RtcEngineContext(
  appId: appId,
  channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));

この xxx インスタンスを、アプリ内で使い回したいという思いがありました。そのため、プロバイダ[1] 内でインスタンス生成・保持しておき、他のプロバイダなどから、ref.watch で呼び出して使い回したいと考えました[2]

問題

※うまく説明できているか分かりませんが、、

① 「Future を返すプロバイダ」の連鎖が複数箇所で発生

このインスタンス生成・保持を プロバイダで行うと、この プロバイダは「Future を返すプロバイダ」になる。次に、この プロバイダを ref.watch で呼び出して使用する側のプロバイダも、「Future を返すプロバイダ」になる。このように、「Future を返すプロバイダ」が連鎖する状況になりました[3]。そして、このインスタンスを複数箇所で利用しようとすると、この連鎖箇所も増えます。そもそも、1 度生成したインスタンスを保持できれば、このような連鎖は発生せず、コードもシンプルになるのでは、という思いがあり、問題に感じていました。

② インスタンス使用時に「non-nullable 指定(!指定)」が必要になる

次に、別のパターンとして、AsyncValue<SharedPreferences インスタンス>を保持するプロバイダを作成して、インスタンスを保持することも試しました[4]「Future を返すプロバイダ」が連鎖する問題は回避できましたが、別の問題が発生しました。AsyncValue の when メソッド にて、 data の場合は、作成したインスタンスを return でき、保持でき、問題ありませんでした。しかし、loading の場合には、インスタンスは保持されていない状態になり、この場合に、null を return する程度しか、私は思いつきませんでした。そして、null を返すようにした場合、今度はこのインスタンスを利用する側で、null だった場合の対応として、non-nullable 指定(!指定)で対応していました。(!指定しなくて済むなら、避けたいという意識を私は持っている。という前提もありますが、)そもそも、1 度生成したインスタンスを保持できれば、!指定不要になるのに、という思いがあり、問題に感じていました。

③ main 関数内でインスタンス生成したら、プロバイダで保持できない

また、main 関数内でインスタンス生成しておくことも考えました。
しかし、ProviderScope ウィジェットの外側なので、プロバイダでインスタンスを保持できない。

解決案

  1. xxx インスタンスを保持するためのプロバイダを事前に用意する。
SharedPreferences sharedPreferencesInstanceProvider = Provider((ref) {
  // ProviderScopeのoverridesで上書きする前提のため、内容は未実装のままで用意する。
  throw UnimplementedError();
}
  1. xxx インスタンス生成用の関数も用意する。
Future<SharedPreferences> createSharedPreferencesInstance() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs;
}
  1. ProviderScope の overrides でプロバイダの内容を上書いて、インスタンス保持させる。
main.dart
runApp(
  ProviderScope(
    overrides: [
      // sharedPreferencesインスタンス生成
      sharedPreferencesInstanceProvider
          .overrideWithValue(await createSharedPreferencesInstance()),
    ],
    child: const MyApp(),
  ),
)

ベストプラクティスの探求 : 2023/03/26 追記

本記事で記載した解決案は、ベストプラクティスではない可能性があります。
下記のツイートを見つけました。

https://twitter.com/remi_rousselet/status/1638254842467450880?cxt=HHwWgICwja6GoLwtAAAA

ツイートのやり取りでは、本記事と同様の課題が質問されているように見えます。

そして、下記の返信があります。

さらに後続のやり取りでは、
https://twitter.com/remi_rousselet/status/1638307092850266114

本記事と同様の解決案がある点、意見が出ています。

しかし、その解決案は、無駄に複雑になるため、止めた方が良い?ようです。

脚注
  1. Provider, StateProvider, FutureProvider, NotifierProvider などの総称として、カタカナ表記で「プロバイダ」と記載しています。 ↩︎

  2. 例で挙げた、shared_preferences の場合、インスタンスはプロバイダで保持して使い回しせず、都度、インスタンス生成すれば良いという考えもあるかもしれません。 ↩︎

  3. 主に、Provider, StateProvider で、インスタンス生成・保持することを試していました。 ↩︎

  4. FutureProvider で、xxx インスタンス生成し、AsyncValue<xxx インスタンス>を返す。そして、AsyncValue<xxx インスタンス>を NotifierProvider で保持する。という組み合わせを試していました。 ↩︎

Discussion