【Flutter】非同期で作成が必要なxxxインスタンスをriverpodで保持し、使い回したい
やりたかったこと
例えば、 shared_preferences や、agora_rtc_engine パッケージ。
下記の通り、インスタンス生成が非同期になっています。
final prefs = await SharedPreferences.getInstance();
_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 ウィジェットの外側なので、プロバイダでインスタンスを保持できない。
解決案
- xxx インスタンスを保持するためのプロバイダを事前に用意する。
SharedPreferences sharedPreferencesInstanceProvider = Provider((ref) {
// ProviderScopeのoverridesで上書きする前提のため、内容は未実装のままで用意する。
throw UnimplementedError();
}
- xxx インスタンス生成用の関数も用意する。
Future<SharedPreferences> createSharedPreferencesInstance() async {
final prefs = await SharedPreferences.getInstance();
return prefs;
}
- ProviderScope の overrides でプロバイダの内容を上書いて、インスタンス保持させる。
runApp(
ProviderScope(
overrides: [
// sharedPreferencesインスタンス生成
sharedPreferencesInstanceProvider
.overrideWithValue(await createSharedPreferencesInstance()),
],
child: const MyApp(),
),
)
ベストプラクティスの探求 : 2023/03/26 追記
本記事で記載した解決案は、ベストプラクティスではない可能性があります。
下記のツイートを見つけました。
ツイートのやり取りでは、本記事と同様の課題が質問されているように見えます。
そして、下記の返信があります。
さらに後続のやり取りでは、
本記事と同様の解決案がある点、意見が出ています。
しかし、その解決案は、無駄に複雑になるため、止めた方が良い?ようです。
-
Provider, StateProvider, FutureProvider, NotifierProvider などの総称として、カタカナ表記で「プロバイダ」と記載しています。 ↩︎
-
例で挙げた、shared_preferences の場合、インスタンスはプロバイダで保持して使い回しせず、都度、インスタンス生成すれば良いという考えもあるかもしれません。 ↩︎
-
主に、Provider, StateProvider で、インスタンス生成・保持することを試していました。 ↩︎
-
FutureProvider で、xxx インスタンス生成し、AsyncValue<xxx インスタンス>を返す。そして、AsyncValue<xxx インスタンス>を NotifierProvider で保持する。という組み合わせを試していました。 ↩︎
Discussion