-
-
Notifications
You must be signed in to change notification settings - Fork 961
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Question]: Same item found in different lists - how can I idiomatically solve the "shared state" problem? #3285
Comments
What does this mean exactly? |
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;
} |
Honestly there's a bit too much to unpack in that issue. But no matter what, imperative overrides are a no-go. There's a reason why |
Okay - true - give me one more chance - let's focus just on part (1) for now if you will
I forgot to mention that
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.
Yes. Sorry.
Honestly I'm fine with whatever. Procedural, Functional, Object-Oriented, Event-based. Fine. |
Sure, pass it around if need be. I'm open to proposals. But I don't think this specific feature request is realistic for technical reasons. Although you always have the possibility to make a Notifier with a public setter in it, and invoke that setter wherever you want. |
Hi, I'm back. I wanted to reason about and experiment on this before wasting your time.
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:
Then you also propose to use a As you've rightfully pointed out, there're many things to unpack. 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 (: |
I also changed the first comment so this issue is narrowed down to a clear and simple problem. I hope this helps. |
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 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 |
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 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 |
Having generated repository for entities sounds fantastic and most likely cover all those cases |
Sorry bout the late relpy! For @mattermoran:
Well ofc. My point is: It seems I can't find an idiomatic way to do so.
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 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. |
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 // 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. |
With normalized data, you have only a single source of truth for what an entity is. There's no case of " |
Yes this is very clear. This is what we're aiming at. This is why we're defining this
I think I might have a know-how gap I might need to fill. 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. The only way - that I currently see - is to have "one single source of truth" is to have an |
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? So I'm wondering if riverpod is ever considering to find a solution for this problem. |
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 |
Thank you for replying so fast! I'm glad to know the issue is acknowledged then 😃 |
Hello there! First of all thank you for solving this. It's been a while, so here's the TLDR of the problem, hoping that you could clarify this.
How can I solve this problem using |
As we are basically talking about related issues (#3781) I am currently facing the same issue. I think how a weak listener can help is as follows (sorry I am on the phone so no code examples): All mutations on an item should happen in the detailed provider/notifier. E.g. your update function on to like an item should be within the ItemNotifier. This way all other providers can weakly listen to the according provider and update their internal state to match it to the ItemNotifier one. So your PaginatedProvider fetched a List which then listens weakly to every ItemNotifier and if this notifier exposes a new state of an item in its list, it will update its state (the list of item) with the updated item. |
In a Flutter application, we have different paginated lists which can possibly overlap, i.e. an
Item
could appear inmyItems(page: 2)
,favoriteItems(page: 0)
,searchedItems(page: 7)
, ...We want a idiomatic, testable and clean way to synchronize shared state (e.g.
isFavorite
state) for all of the above without adding extra BE requests.There's a complete example available to reproduce the question above.
EDIT. Originally, this issue proposed an imperative API, which is rejected by Riverpod's declarative nature. The original proposal has been edited away from this issue.
The text was updated successfully, but these errors were encountered: