I’m using Riverpod to listen to changes to a selected item from my store:
- I have a repository
PlacesStore
which has aStream<PlaceDetails?> watchPlace(PlaceId placeId)
method. - I have wrapped this in a family provider like so:
@riverpod Stream<PlaceDetails?> place(PlaceRef ref, InternalPlaceId placeId) { final store = ref.watch(placesStoreProvider); return store.watchPlace(placeId); }
- I have a separate
StateProvider
,selectedPlaceIdProvider
, which is updated with a place ID of a place the user selects on the map. - I have another provider,
selectedPlaceProvider
, which combines both like this:@riverpod Stream<PlaceDetails?> selectedPlace(SelectedPlaceRef ref) { final placeId = ref.watch(selectedPlaceIdProvider); if (placeId != null) { return ref.watch(placeProvider(placeId).stream); } return const Stream.empty(); }
I was doing it like this because I have a number of widgets which want to know what place the user has selected, so I thought it made sense to pull that into a provider which contains the currently selected place details (while still maintaining the family provider so adhoc places can be looked up).
However, when using ref.watch(placeProvider(placeId).stream)
I am warned that .stream
is deprecated. I don’t quite follow what I am meant to replace it with.
Is the above pattern valid? And if so, what do I replace .stream
with so I can still listen to any changes to my currently selected place.
2
Answers
You really never need a stream from a provider. If you want a view or a provider to depend on other provider changes, simply subscribe with
ref.watch
orref.listen
.Streams have no place in state management. Streams are useful for places where having every single intermediate state is important to record, like a log or a bank transaction ledger. But in state management, having the latest value is all that’s important. You merely need your view or your provider to consume the other provider’s latest value, and you’re done.
And
.stream
was removed, because it made some other parts of the code difficult. If you actually need a stream, you can listen to a provider, and generate your own stream using your rules.The
.stream
modifier has been deprecated because it almost always was badly used, leading to complex hard-to-spot bugs.The very snippet you gave in this question faces the same issue. The snippet you gave has a bug:
In this snippet, the usage of
.stream
introduces a problem where now the behavior ofselectedPlace
depends on the order in which your providers are read.Say you did:
Then in this scenario, you’ll find that the
print
is in fact never reached, andselectedPlace
never emits anything.But if you were to comment out the
final place = await container.read(placeProvider(123).future);
line, then your code would "work".That effectively means using
.stream
introduced a race condition. It’s very hard to spot unless you’re familiar with the issue, and can cause significant maintainability problems.On the flip side of things, having
.stream
doesn’t add much value to Riverpod. You can already do everything without.stream
.The Changelog that introduced the deprecation gives alternate syntaxes to your usage https://github.com/rrousselGit/riverpod/blob/master/packages/riverpod/CHANGELOG.md#230
In your case, you could refactor
selectedPlaceProvider
to:This snippet behave like you would expect the .stream variant to behave. In that scenario,
selectedPlace
will update whenever eitherplaceProvider
orselectedPlaceIdProvider
update.But at the same time, this doesn’t involve the race-condition problem discussed above. That
main
wrote previously would now work as expected.