🦦
Riverpod v2ではStreamProviderの扱いに気をつけよう
これは何?
GitHubのissueでバグっぽい挙動を報告したら想定通りだったという話です。
どういうこと?
業務で作ってるアプリのある部分で、StreamProvider
を使うときに、データソースになる Stream
が分岐するという状況がありまして。
例えばこんな感じ↓
final originalStream = StreamProvider.family.autoDispose<String, String>((ref, prefix) {
return Stream.value('$prefix foo');
});
final prefixStore = StateProvider.autoDispose<String?>((_) => null);
final originalListener = StreamProvider.autoDispose<String>((ref) {
final prefix = ref.watch(prefixStore);
if (prefix == null) {
return const Stream.empty();
}
return ref.watch(originalStream(prefix).stream);
});
issueに書いてある再現コードと同じですがw
このコード、要は引数を元に Stream
を生成する originalStream
を StateProvider
に依存しながら呼び出すというコードなんですが、これを使ったときに Riverpod v1 系と v2 系で挙動が異なります。
こんな感じのコードを書いて検証してみました。
void main() async {
final container = ProviderContainer();
final value = await container.read(originalStream('#').future);
print('first value: $value');
container.listen(
originalListener,
(_, value) {
print('$value');
},
fireImmediately: true,
);
container.read(prefixStore.notifier).state = '#';
}
v1 系ではデータソースが切り替わったタイミングで AsyncLoading
を挟んだ後、前の Stream
のデータを読み取ることが出来ます。
first value: # foo
AsyncLoading<String>()
AsyncData<String>(value: # foo)
対して v2 系では、データソースが切り替わると AsyncLoading
の後は何か値が emit されない限り読み取ることが出来ません。
first value: # foo
AsyncLoading<String>()
v2 系の AsyncLoading
は内部に値を持つことができる(UIのリフレッシュ中などに画面にデータを表示しながら AsyncLoading
にできるという想定)ようになっていますが、このような状況だと内部に値も持っていません。
ではどうすれば?
StreamProvider
を読み取ったときの値は AsyncValue
なので、素直に AsyncValue
を返すような Provider を作りましょう。
final originalListener = Provider.autoDispose<AsyncValue<String>>((ref) {
final prefix = ref.watch(prefixStore);
if (prefix == null) {
return AsyncLoading();
}
return ref.watch(originalStream(prefix));
});
あるいは、AsyncLoading
や AsyncError
が伝わらなくてもいい(伝えたくない)場合は FutureProvider
として StreamProvider.future
を返すのも手です。
final originalListener = FutureProvider.autoDispose((ref) {
final prefix = ref.watch(prefixStore);
if (prefix == null) {
return null;
}
return ref.watch(originalStream(prefix).future);
});
Discussion