Comments (17)
Note that 3.0 is already adding a way to listen a provider without mounting it:
ref.listen(
provider,
weak: true,
(prev, next) {
});
This should help solving this sort of problem. An Item
provider could listen to multiple providers emitting an Item
instance and pick the most recent one, without triggering those providers.
So you could do:
@riverpod
Future<List<Item>> home() => get('/home');
@riverpod
Future<Item> byId(Ref ref, {required String id}) {
ref.listen(
homeProvider,
weak: true,
(_, next) {
final value = next.valueOrNull?.firstWhereOrNull((item) => item.id == id);
if (value != null) ref.state = value;
});
}
Of course, this is fairly low-level. I'm thinking about adding a higher-level API around this to make it simpler.
In particular, I'm considering adding a "Repository" automatically generated by riverpod_generator
, which would handle a full CRUD for an entity, along with paginated APIs.
from riverpod.
The problem is by no means unique to Riverpod. It's a general software issue
That doesn't mean Riverpod won't try to solve this. That's what Riverpod does anyway, trying to solve general software issues. That's why Riverpod also will add things like offline persistence & stuff. Not unique to Riverpod, but good to have
from riverpod.
The main takeaway is: there is no easy way to pass in state from a provider to its "child" (sort-to-say).
What does this mean exactly?
Isn't that what providers do, passing state to their children?
from riverpod.
Solving (1) would look as easy as:
@riverpod class IsFavoriteController extends _$IsFavoriteController { @override int build(int id) => throw UnimplementedError('This provider is meant to be overridden'); } @riverpod FutureOr<List<Item>> someList(SomeListRef ref) async { final result = fetch(fromSomewhere); for (final item in result.items) { ref.override(isFavoriteControllerProvider(id), w: (ref) => item.isFavorite); } }
Why can't you do:
class IsFavoriteController extends _$IsFavoriteController {
@override
bool build(int id) => ref.watch(someListProvider).items.firstWhereOrNull((i) => i.id == id)?.isFavorite ?? false;
}
from riverpod.
Honestly there's a bit too much to unpack in that issue.
It's not immediately clear to me what we're trying to solve. It's hard to redirect you to another solution or issue as I'm not fully sure what this is about. It'd help if you could give smaller examples than the repo you linked. Your example has way too much code for me to know what exactly you're trying to showcase here.
But no matter what, imperative overrides are a no-go.
No matter whether we would want those, we cannot have those. It is quite fundamental to how Riverpod works that overrides are fully static.
There's a reason why ProviderContainer
/ProviderScope
don't enable adding/removing new overrides. It doesn't work :)
from riverpod.
Honestly there's a bit too much to unpack in that issue.
Okay - true - give me one more chance - let's focus just on part (1) for now if you will
Why can't you do [...]
I forgot to mention that someListProvider
is paginated - every list in these example is, even the favorite one.
- what's the correct page to watch? Should I pass
page
around everywhere - which is not really feasible for my use case. (page
leaks everywhere - it not just tedious but... you quickly find yourself with widgets or provider needing apage
they don't really care about) - accessing lists is generally unsafe - especially the
?? false
part: you can never really rely onfirstWhere
, e.g. invalidation refresh the provider and removes an item, or any other unexpected change. Generally speaking, the mental reactive model I have should watch this detail and not the other way around
It's not immediately clear to me what we're trying to solve.
I'm trying my best to summarize my use case, but without the right constraints, riverpod has a solution for the above problem. With the right requirements, I find myself in a pinch and I'm not sure this issue is open in here.
Give me one more chance with a bullet point:
- You want to handle several list of items, paginated, possibly infinite, that have some common properties that need to be in sync!
- I can't find an idiomatic way to "always find app state in sync between multiple providers (or controllers) that reference the same items (semantically, e.g. same id)"
- "in sync" includes and isn't limited to the following events: some items are removed from the favorites (they disappear from a list and their favorite status should be propagated everywhere), some items are added from the favorites (they appear in the favorite list, propagate it), some items are deleted entirely (etc.)
Your example has way too much code
Yes. Sorry.
Don't look at the code - execute the trivial app and notice how an extra request is performed when you tap a like button, then - if you may - read the summary above again to understand why I must perform that to synchronize state everywhere.
But no matter what, imperative overrides are a no-go.
Honestly I'm fine with whatever. Procedural, Functional, Object-Oriented, Event-based. Fine.
I need to ship, ship, ship features. Test them. And "fast" (aka at the right speed).
I'm all-in for reactivity - I guess you know 😛 - but I can't solve this problem.
Maybe a reactive emit
will do (aka let provider register callback listeners that are triggered on emits from below the tree)? I'm not sure. I'm all ears
from riverpod.
- what's the correct page to watch? Should I pass
page
around everywhere
Sure, pass it around if need be.
If may be inconvenient by your standards, but it works.
I know that you dislike props drilling, as we've discussed variants of this multiple times. But that's ultimately the current solution.
I'm open to proposals. But I don't think this specific feature request is realistic for technical reasons.
If you have other ideas, I'm open to investigate those.
Although you always have the possibility to make a Notifier with a public setter in it, and invoke that setter wherever you want.
You'll break a few Riverpod principles and may have to use workarounds to bypass the internal error handling, but that should work.
from riverpod.
Hi, I'm back. I wanted to reason about and experiment on this before wasting your time.
Sure, pass it around if need be.
I don't mind passing around props anymore, since our last discussion I accepted that 😄 I tried doing that, but it doesn't solve the problem.
You proposed:
class IsFavoriteController extends _$IsFavoriteController {
@override
bool build(int id, int page) => ref.watch(someListProvider(page)).items.firstWhereOrNull((i) => i.id == id)?.isFavorite ?? false;
}
But this won't work, because:
- an item could be in
searchProvider(page: 5)
, intomyFavoritesProvider(page: 0)
, and intomyListProvider(page: 1)
. See? Passingpage
around gives inconsistent states, because.firstWhereOrNull
will returnnull
in most cases, thusfalse
even if it's favorited - say we solve (1), somehow. Again, we have
myListProvider
,myFavoritesProvider
,searchProvider
,homeProvider
,anotherListProvider
. They're all asynchronousList<T>
fetchers. Question: which of these shouldIsFavoriteController
watch?
Then you also propose to use a Notifier
just to have a public setter in it, but as you've pointed out that clearly breaks riverpod usage. I also am not sure how this Notifier
should be initialized (null
? yuck!)
As you've rightfully pointed out, there're many things to unpack.
Therefore, can we narrow this down to solving this "shared state" problem?
I am not able to understand how riverpod can help me here. Shared state is exactly why I'm adopting riverpod, hence this issue. So, thank you for replying up until now (:
from riverpod.
I also changed the first comment so this issue is narrowed down to a clear and simple problem. I hope this helps.
from riverpod.
This applies to essentially any app out there. And it's very common to see inconsistent state in the app because it's changed in one place but not other (especially inside lists).
The way I handle it is by essentially having a single provider per Item
. And instead of rendering List<Item>
you render List<Reference<Item>>
so the list is always up to date. In this case my whole app is synchronized. Doesn't matter if I have 100 different lists where that item is present. They all will render exactly same thing. And once one of the properties is updated like isFavorite
because button was clicked or just because new state comes in a new list that will be reflected everywhere.
As to how exactly to do this is up to you.
In my case I use graphql, hook into response coming from api, and then place it into my cache that is then read by a provider.
This is a very basic explanation as I do bunch of other stuff like normalizing data to handle deeply nested objects, merging data so incompletely data doesn't just override, etc.
The comes with bunch of other advantages like navigating around the app with just ids as you always know where to get that data from, "free" offline mode as you already have the data cached, etc
But you can keep is simple and skip normalization if you have simple enough app and use-case.
I'm sure I can make it into a package. It's very generic and technically could be used with any "provider" package out there be it riverpod, bloc, or anything
from riverpod.
Having generated repository for entities sounds fantastic and most likely cover all those cases
from riverpod.
Sorry bout the late relpy! For @mattermoran:
As to how exactly to do this is up to you.
Well ofc. My point is: It seems I can't find an idiomatic way to do so.
In my case I use graphql, hook into response coming from api, and then place it into my cache that is then read by a provider.
See? That's the point. Riverpod is A Reactive Caching and Data-binding Framework. So why must we re-implement our own cache, again? (I suppose via a database / persistent storage of some kind).
Anyways, your following comment brings up a suggestion for @rrousselGit:
This is a very basic explanation as I do bunch of other stuff like normalizing data to handle deeply nested objects, merging data so incompletely data doesn't just override, etc.
This comes with bunch of other advantages like navigating around the app with just ids as you always know where to get that data from, "free" offline mode as you already have the data cached, etc
This should be addressed in the documentation: if Riverpod is meant to be used with some data normalization, or any best practice really, it should be explicitly told in a dedicated documentation page.
from riverpod.
And about riverpod 3's new API @rrousselGit just showed: again, I'm not sure how this solves the above problem.
The main pain point of this issue is: even after data normalization, I am unable to determine which provider should be watched. Here you're suggesting something like:
// weak listen is great, but we're still referencing one single list
watched.valueOrNull?.firstWhereOrNull((item) => item.id == id);
This proposal is cool, but because of points (1) and (2) of my message above, but I can't see how it addressed the "synced state problem".
Because of data normalization, I would love to do the following instead:
@Riverpod(lazy: true)
int favoriteId(FavoriteIdRef ref, int id) {
return Uninitialized<int>; // sentinel value, or something similar
}
// somewhere else
@riverpod
List<int> someList(SomeListRef ref) async {
final elements = await fetch();
for (final element in elements) {
ref.listen(favoriteId(id).initializeWith(element.favoriteId), (previous, next) {...});
}
}
But I'm not sure this is even doable. Maybe with metaprogramming we could define uninitialized / lazy providers via typedef
s annotations, somehow:
// defining a typedef instead of a function means defining a provider with no initialization (aka lazy)
@riverpod
typedef FavoriteIdProvider = int Function(Ref ref, int id);
And let static metaprogramming do its thing. But again, I'm being imaginative here.
Honestly, any working suggestion is welcome. Besides implementing my own second cache on my own, that is 😄
from riverpod.
With normalized data, you have only a single source of truth for what an entity is.
There's no case of "Item
in home vs Item
in detail page". They all should be using the exact same instance.
There shouldn't be a case where mutating one Item(id: 42)
isn't reflected on other Item(id: 42)
, since there's only a single instance of it.
from riverpod.
There shouldn't be a case where mutating one Item(id: 42) isn't reflected on other Item(id: 42), since there's only a single instance of it.
Yes this is very clear. This is what we're aiming at. This is why we're defining this ItemController
with the "change favorite status mutation" on it.
The question in this thread is: how? (i.e. with riverpod)
The .firstWhereOrNull
solution might bring to inconsistent state - if I've understood this correctly - so it's not really viable. Also, which list and which page should I watch
?.
They all should be using the exact same instance
There's no case of "Item in home vs Item in detail page"
I think I might have a know-how gap I might need to fill.
Please, I'd be thankful if you'd help me out with this / point me towards some readings afterwards.
But.
Here's my POV, with a pragmatic problem to solve:
These two lists do come from semantically different sources, tho. Say I set to favorite my own item.
Such item now belongs to faovritesProvider(page: 5)
, while being on myItemsProvider(page: 2)
, but also casually appearing on homeProvider(page: 0)
.
The only way - that I currently see - is to have "one single source of truth" is to have an ItemController
, but - again - the problem is its initialization..
from riverpod.
Hi 😸
I'm back on this issue, I re-read the whole conversations. Me and some colleagues tried some solutions towards this "synced-state-problem", but nothing is really working out.
I'm just curious - is this really a "riverpod" problem? Or is it a more generalized problem?
Because I can see our issue reproduced on several social platforms, e.g. Twitter's like system.
So I'm wondering if riverpod is ever considering to find a solution for this problem.
Or if it's relevant to riverpod at all.
Answering this would greatly help our R&D process. We might change everything just to tackle this one out.
And, consequently, we might close this issue with a "wont-fix" label.
from riverpod.
Thank you for replying so fast!
I'm glad to know the issue is acknowledged then 😃
from riverpod.
Related Issues (20)
- ImageProvider generated as InvalidType HOT 4
- `AutoDisposeProvider` is getting disposed when watched in a `StreamProvider` using `async*` HOT 1
- Provider container loses data in widget test HOT 4
- AutoDisposeNotifierProvider causing state loss HOT 3
- Reword `The ref.watch method should not be called asynchronously`
- Add a DevTools extension for riverpod HOT 2
- Broken update HOT 3
- [riverpod 3] NotifierProvider doesn't work with a NotifierBase mixin.
- riverpod_generator does not respect import aliases HOT 1
- Ref onError callback HOT 1
- `ref.exists()` returns `true` when manually invalidate a provider.
- `yarn dev -l {LANG}` is needed when hosting documentation website locally for specific locale with docusaurus
- Stream from StreamProvider is not unsubscribed to when widgets get disposed
- There is no way to handle the "done" event on the stream of a StreamProvider
- `ref.invalidate(familyProvider)` is not working with scoped providers
- PageController stateprovider doesn't work when .previousPage() or nextPage() is pressed
- beforeDispose or beforeInvalidate hook, happen before invalidate HOT 5
- Multiple invalidation of Providers
- Unable to use the latest riverpod in flutter version lower than 3.16 HOT 2
- The getter 'variable2' isn't defined for the class 'PropertyAccessorElement'.
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from riverpod.