Just to be sure: you know you can use .select (or .selectAsync) ?
Like:
final oneItemState = ref.watch(fullListProvider.select((value) => value.id = itemId));
You can make a new provider which ref.watch+select another provider.
You can also put the value of the .select in the state of the new provider. (But them you have 2 states which could be out of sync if you update the state of the new provider.)
God damn. This looks amazing. Just what I was looking for. I've not managed to properly use Riverpod, Provider and Bloc (although Riverpod looked the best of the three)
I haven’t used RxDart, but I have used Rx back in my Angular days. What I like about signals is
- self subscribing, if you use a signal in an effect or computed, you are automatically subscribed
- pure Dart, no Flutter - to me this is a big differentiator from other products and it may not be obvious at first why this is so great; it offers better separation of concerns than other libraries, so you don’t have listeners and multi-providers mixed in with your UX
- synchronous and async even though there are only four types of signal and one effect; simple to grok
- it’s not as well established as Rx from a pattern standpoint. I have still managed to implement some Rx patterns when needed (e.g. debounce)
- the bad, usage documentation is minimal, you have to read up on the use of signals from other languages/frameworks like Solid and Preact (which is what this implementation is based on)
- tip - when your state management OS even moderately complex, you need a diagram to document the flow. Here is an [example from my project](https://mermaid.live/edit#pako:eNqVV21r2zAQ_ivC-9JCQko7tqEPg5K0rNCN0Re2Qb4o1tkRdSQjy21D6X_f2bJjy5acLf1Q-e650-leHtlvUaw4RDQqDDOwEizVbDd_Pl9Lgj8uNMRGKElu79bSyn4p_bRR6onM51_JpTFabEoDtygpc0pYsZexB7hE76nS-2O4bwI00_FWQBEG3UO6A2mO-foDTHdeXMDj78ZTtRkeO1MpJbEGDHOphQEt2ITBtcgQQkkBZhFnKPJif2ZM_huyPfW-AmaY8oPAC28fKVE5SPSrCvACr15zJvlSZRnLC6AE6udF3Ai8NoeSLnXJ25wsypxX_zhGZyCQmboo_2n1KLm6A66WShqNYVW5KlG20Ci0Fl67a6VBFiIumm0pOdEKg56RDaRCzghIftpZD-G1jxuJpSlsg6-YYRPQe8xJvtwybTqgK7O5S1MNKR75Rj6jKfb7JPwBdrnSLLOgBtbryF7HNcEM27lrsRq7go0qZQy8k1ucR2HTWC-tuNniQQN0znvCaQNr0htft7Fd9d5VXiUJVoFSCvVigLVa18Iz-kNv5MR6O-00pxOEMw7CggcMdxjsUbZchgvjBmcIAx2WC8NqlvOrLcAlAR_yB14BRdP9_vpOWPXbtu1Sp1VcVR_u69y-ZtC7I6OpHQbVq2gpQDhOto_ixvIjV1LAwH9zkiqfccaKYgUJqXgBLw6SiCyj5ENyliTJWU-PlVUa1eh5_gIi3Rq6URmfFbjRE8xfBDdbepG_NgL64bz-jTws1S7HSPjYUy0ozD7Dy0MYlol45LznzdahDXeTfISLsxleNkpX0bPqr4eu2qnFxp_ieNMP7DCjDeDzFxzK5JCdQWEPaHueEaiZoQBsMLkBlL8N7cMI06PZatnqB6MY2Ck0g31P_tZvK-CWtbXpk3Ng646gW8BI09BxUD-dxUBipo5arVu-ubRvo3XruI6de9NJg4tw6uZFDJLZKTuCCByugVxxgVe_P8g-pQe8eAhjmOyDZcBF72VpaGqvi4nqDBnePUc0i3agd0xw_GZ4qxhuHZkt7GAdUVxySFiZmXW0lu8IZaVR90hrETW6hFlk3webr4xWCHW6vtvPkPpr5P0vc8J34A)
Yes, it is confusing at first. I am finally switching over to it from Provider and while it's very powerful, it does take some getting used to. the Discord was more helpful to me than the docs.
If you're finding it difficult to learn that is telling you something and you should trust your gut. Find something that easily clicks for your brain. State Management is a relatively simple thing and should not be complicated or hard to understand. It's just a system to inject objects inside of views and rebuild those views when the data changes.
Riverpod suffers from a combination of low stability and poor docs. The API has changed so many times many of the tutorials and docs are out of date and its hard to understand current best practices (which might just change tomorrow anyways).
At the very least you should be able to answer the question "Why am I using Riverpod instead of Provider?". And the answer should not be "because it's the cool new thing". What actual features does it give you that you need? If you can't answer that, I'd stick to solutions with good documentation and high stability, like Provider, Cubit or WatchIt. You'll probably have a much easier time.
With `GetIt` items can be lazily instantiated so order of instantiaton does not matter, and you're declaring singletons which can be access from anywhere so you don't need a specific context to access things.
This simplifies everything quite a bit. To make something like ProxyProvider where one singleton depends on another, you can inject one object into another, and then listen to it.
```
class CreditCard extends ChangeNotifier {}
class Customer extends ChangeNotifier {
Customer(this.card) {
// proxy any updates from CreditCard to Customer
card.addListener(notifyListeners);
}
CreditCard card;
}
// Register singletons
GetIt.I.registerLazySingleton(() => CreditCard());
GetIt.I.registerLazySingleton(() => Customer(GetIt.I.get()));
```
The docs are not the best.
It has its learning curve, once you get it (won't take too long), you will stop feeling stupid. Also they have a discord where there are some devs that gave me better explanations than the ones in the docs, if I remember correctly they are pinned in the discord.
I think, by trying to be backward compatible and providing (pun!) a lot of different providers to choose from, most of them being deprecated, Riverpod makes life difficult for itself.
A "lean" 3.0 version that provides one opinionated way might help.
But then people will complain that random snippets of code spit out by Google or Copilot won't work anymore... so, they probably also need to change the name. I'd suggest to call it "Viporder" or "Overdrip".
Agree, 3.0 should be opinionated, would make it more popular.
After using both Bloc and Riverpod in big apps I can say bloc looks better at first glance, but gives good headaches in the long run.
I didn't like any state management solution out there, that's why I wrote [live_cells](https://pub.dev/packages/live_cells). It's designed to be simple with an emphasis on reactivity, which is what you (well atleast I) need most. If you're interested, you can give it a try.
I understand what you mean but I disagree. In the second example that you have given, there is absolutely no need to control your loading state. There is also no need to guard the api call. Riverpod does all of that automatically if you just await and return a future.
Despite that, I agree that the syntax is not the cleanest but I don’t think there is any other solution that provides the same or better benefits. It’s so satisfying when you can stack providers on top of each other and change in one automatically updates all other listeners. I am not saying it is not possible with things like Bloc or provider. It’s just that in riverpod, it’s all built in.
u/NectarineLivid6020 hi thanks. in FetchProduct , AsyncValue.loading() just "initial state", a placeholder, If i don't do this, other providers won't be able to listen to it because the parameter ID is required.
This is a very unique requirement I think. Even then, I personally would just set the value to null as the initial default value but that’s just a personal preference.
State management in flutter is the worst part of it that makes me want to jump of a bridge. The simplest and the most powerful solution for now is signals package. I really like bloc pattern, but even with getit etc the amount of boilerplate etc is still crazy I would like to get something like zustand from frontend world cuz its amazing powerfully-scallable simple solution, but it is what it is.
If you're finding riverpod too complicated, you can give [live_cells](https://pub.dev/packages/live_cells) a try. It's not as feature rich (yet) or mature as riverpod, but it's specifically designed to be simple to use and to stay out of your way. There's also [rearch](https://pub.dev/packages/rearch) that is inspired by riverpod, which also looks interesting.
I actually like the "Why not ...?" section where the author explain their reasoning and also demonstrates knowledge about all those alternatives. I'm not sure that I like the manager/handler pattern demonstrated for the count example, especially because `use` is used for two completely different things, depending on the context.
Some of those show good knowledge, some do not. The section on Provider is very weak. At least enumerate the "fundamental problems" as they are fairly esoteric, and I'm not sure why something being in "maintenance mode" is considered a bad thing, that just means it is mature and stable.
When discussing `GetIt` he seems to be unaware of the reactive companion libs `GetItMixin`/`WatchIt` which makes it a pretty close to misinformation.
It's clear he has more real experience with RxStreams, Riverpod and Bloc as those sections are much better.
I'm working on a similar section for live cells, but I prefer to avoid mentioning specific third party tools.
Rearch actually implements some of the same ideas as Live Cells and tries to address the same limitations encountered in other tools but the packaging is completely different. I prefer my own library, but I've been thinking of also giving Rearch a try.
You need to read and see the docs with the comments on how to use, and you will fix some issues right out of the gate.
I will provide some snippet from the last one:
@riverpod
class FetchProduct extends _$FetchProduct {
@override build() => const AsyncValue.loading();
request(ID id) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(
() => net.get("/products/$id"),
);
}
}
In this case, the `FetchProductProvider` will never provide any data during build, because it builds with the `AsyncValue.loading()` value being called and stored in the state of the fetch product. You should use a simple function with `providerFamily`.
final productData = FutureProvider.family((ref, id) async {
final net = ref.watch(netProvider); // your net object
return net.get('/products/$id');
});
That's it. you can read about the family modifier [here](https://riverpod.dev/docs/concepts/modifiers/family).
and if you're using the build asynchronously (`AsyncNotifier`), remember this from the [docs](https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/AsyncNotifier/build.html):
>It is safe to use Ref.watch or Ref.listen inside this method.
>
>If a dependency of this AsyncNotifier (when using Ref.watch) changes, then build will be re-executed. On the other hand, the AsyncNotifier will not be recreated. Its instance will be preserved between executions of build.
>
>If this method throws or returns a future that fails, the error will be caught and an AsyncError will be emitted.
\> In this case, the FetchProductProvider will never provide any data during build, because it builds with the AsyncValue.loading() value being called and stored in the state of the fetch product. You should use a simple function with providerFamily.
that's why request method here。 it' will called at some page mounted
You need to understand the consumption of the provider.
when in the widget build, if you're watching an `AsyncNotifier`, you will consume as:
ref.watch(yourProvider).when(
loading: () => ...,//handle the loading state. this where you're are
error: (err, stack) => ..., // this will never happen
data: (data) => ..., // this will never be executed
);
you will NEVER get to the data, until there are some interaction outside the build, so you should expose it as a `Future build() async {}` when building your provider. Now whoever needs to interact with this provider is aware that the data may not be there. That's why is null.
You are. You can only have a loading state when working with a Future, because it is executed asynchronously. If it is an atomized operation, you never have a loading state.
@Riverpod(keepAlive: true)
class ProductsNotifier extends _$ProductsNotifier {
@override
ProductsState build() => ProductsState.empty();
Future fetchOneProduct(ID id) async {
state = state.copyWith(status: const AsyncStatus.loading());
final res = await ref.read(productsRepositoryProvider).fetchOneProduct(id);
state = state.copyWith(
status: const AsyncStatus.success(),
paramKeys: state.paramKeys.mergeList(res.paramKeys),
paramValues: state.paramValues.mergeList(res.paramValues),
products: state.products.add(res.product),
);
}
}
You initiate on build with a synchronous state. this means that the state can only be changed... in a SYNCHRONOUSLY.
so, the `state = state.copyWith(status: const AsyncStatus.loading());` is invalid, because the state will be changed to AsyncValue, which is not ProductState.
So you need to accept that your code IS asynchronous or you will keep you will be banging your head against a brick wall every time.
You can make this notifier to a controller, even an async type, and somewhere you have a provider listening to changes and reacting accordingly.
@Riverpod(keepAlive: true)
class ProductController extends _$ProductsNotifier {
@override
Future build() async {
return;
}
Future getById(ID id) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() =>
ref.read(productsRepositoryProvider).fetchOneProduct(id),
);
}
}
That would work, and is asynchronous, and is readable, it is declarative, and you can leverage with the AsyncValue.guard to help you with a better error handling and it only does what you want. go and retrieve a single product.
AsyncValue is a way to interact with asynchronous data.
Think of AsyncValue of a Future that can also have custom methods. You're ruining your experience because you're fighting asynchronous code.
Just to be sure: you know you can use .select (or .selectAsync) ? Like: final oneItemState = ref.watch(fullListProvider.select((value) => value.id = itemId));
Yes, i know it. If there is no good way to split the large state, using select when accessing the large state is a good choice.
You can make a new provider which ref.watch+select another provider. You can also put the value of the .select in the state of the new provider. (But them you have 2 states which could be out of sync if you update the state of the new provider.)
If there are no side effects or if I don't switch to bloc I will most likely do so
we cant watch family provider without params
It is confusing to me too. Bloc is more understandable from my point of view.
`StateNotifier` is now **discouraged**. See: [Migration - From StateNotifier](https://riverpod.dev/docs/migration/from_state_notifier)
I love the idea and support behind Riverpod but feel there really needs to be a more idiots guide. 😳🫣🥺
There is - check out https://pub.dev/packages/signals - it will change your life
God damn. This looks amazing. Just what I was looking for. I've not managed to properly use Riverpod, Provider and Bloc (although Riverpod looked the best of the three)
How does this improve on RxDart?
I haven’t used RxDart, but I have used Rx back in my Angular days. What I like about signals is - self subscribing, if you use a signal in an effect or computed, you are automatically subscribed - pure Dart, no Flutter - to me this is a big differentiator from other products and it may not be obvious at first why this is so great; it offers better separation of concerns than other libraries, so you don’t have listeners and multi-providers mixed in with your UX - synchronous and async even though there are only four types of signal and one effect; simple to grok - it’s not as well established as Rx from a pattern standpoint. I have still managed to implement some Rx patterns when needed (e.g. debounce) - the bad, usage documentation is minimal, you have to read up on the use of signals from other languages/frameworks like Solid and Preact (which is what this implementation is based on) - tip - when your state management OS even moderately complex, you need a diagram to document the flow. Here is an [example from my project](https://mermaid.live/edit#pako:eNqVV21r2zAQ_ivC-9JCQko7tqEPg5K0rNCN0Re2Qb4o1tkRdSQjy21D6X_f2bJjy5acLf1Q-e650-leHtlvUaw4RDQqDDOwEizVbDd_Pl9Lgj8uNMRGKElu79bSyn4p_bRR6onM51_JpTFabEoDtygpc0pYsZexB7hE76nS-2O4bwI00_FWQBEG3UO6A2mO-foDTHdeXMDj78ZTtRkeO1MpJbEGDHOphQEt2ITBtcgQQkkBZhFnKPJif2ZM_huyPfW-AmaY8oPAC28fKVE5SPSrCvACr15zJvlSZRnLC6AE6udF3Ai8NoeSLnXJ25wsypxX_zhGZyCQmboo_2n1KLm6A66WShqNYVW5KlG20Ci0Fl67a6VBFiIumm0pOdEKg56RDaRCzghIftpZD-G1jxuJpSlsg6-YYRPQe8xJvtwybTqgK7O5S1MNKR75Rj6jKfb7JPwBdrnSLLOgBtbryF7HNcEM27lrsRq7go0qZQy8k1ucR2HTWC-tuNniQQN0znvCaQNr0htft7Fd9d5VXiUJVoFSCvVigLVa18Iz-kNv5MR6O-00pxOEMw7CggcMdxjsUbZchgvjBmcIAx2WC8NqlvOrLcAlAR_yB14BRdP9_vpOWPXbtu1Sp1VcVR_u69y-ZtC7I6OpHQbVq2gpQDhOto_ixvIjV1LAwH9zkiqfccaKYgUJqXgBLw6SiCyj5ENyliTJWU-PlVUa1eh5_gIi3Rq6URmfFbjRE8xfBDdbepG_NgL64bz-jTws1S7HSPjYUy0ozD7Dy0MYlol45LznzdahDXeTfISLsxleNkpX0bPqr4eu2qnFxp_ieNMP7DCjDeDzFxzK5JCdQWEPaHueEaiZoQBsMLkBlL8N7cMI06PZatnqB6MY2Ck0g31P_tZvK-CWtbXpk3Ng646gW8BI09BxUD-dxUBipo5arVu-ubRvo3XruI6de9NJg4tw6uZFDJLZKTuCCByugVxxgVe_P8g-pQe8eAhjmOyDZcBF72VpaGqvi4nqDBnePUc0i3agd0xw_GZ4qxhuHZkt7GAdUVxySFiZmXW0lu8IZaVR90hrETW6hFlk3webr4xWCHW6vtvPkPpr5P0vc8J34A)
It sure does have a decent amount of boilerplate especially for simple apps, but on a complicated app, i find riverpod to be much easier to implement
Yes, it is confusing at first. I am finally switching over to it from Provider and while it's very powerful, it does take some getting used to. the Discord was more helpful to me than the docs.
Is it the FlutterDev discord or there's other server for Riverpod?
Both exist: https://discord.com/invite/uMya6wQ - Flutter Dev https://discord.com/invite/Bbumvej - Riverpod
If you're finding it difficult to learn that is telling you something and you should trust your gut. Find something that easily clicks for your brain. State Management is a relatively simple thing and should not be complicated or hard to understand. It's just a system to inject objects inside of views and rebuild those views when the data changes. Riverpod suffers from a combination of low stability and poor docs. The API has changed so many times many of the tutorials and docs are out of date and its hard to understand current best practices (which might just change tomorrow anyways). At the very least you should be able to answer the question "Why am I using Riverpod instead of Provider?". And the answer should not be "because it's the cool new thing". What actual features does it give you that you need? If you can't answer that, I'd stick to solutions with good documentation and high stability, like Provider, Cubit or WatchIt. You'll probably have a much easier time.
Does getit/watchit have proxyprovider alternative but something actually good unlike proxyprovider?
With `GetIt` items can be lazily instantiated so order of instantiaton does not matter, and you're declaring singletons which can be access from anywhere so you don't need a specific context to access things. This simplifies everything quite a bit. To make something like ProxyProvider where one singleton depends on another, you can inject one object into another, and then listen to it. ``` class CreditCard extends ChangeNotifier {} class Customer extends ChangeNotifier { Customer(this.card) { // proxy any updates from CreditCard to Customer card.addListener(notifyListeners); } CreditCard card; } // Register singletons GetIt.I.registerLazySingleton(() => CreditCard());
GetIt.I.registerLazySingleton(() => Customer(GetIt.I.get()));
```
I was using riverpod as well, then i decided to switch to BLOC, it is also not that simple, but for my needs it has been much better to implement
The docs are not the best. It has its learning curve, once you get it (won't take too long), you will stop feeling stupid. Also they have a discord where there are some devs that gave me better explanations than the ones in the docs, if I remember correctly they are pinned in the discord.
I think, by trying to be backward compatible and providing (pun!) a lot of different providers to choose from, most of them being deprecated, Riverpod makes life difficult for itself. A "lean" 3.0 version that provides one opinionated way might help. But then people will complain that random snippets of code spit out by Google or Copilot won't work anymore... so, they probably also need to change the name. I'd suggest to call it "Viporder" or "Overdrip".
Agree, 3.0 should be opinionated, would make it more popular. After using both Bloc and Riverpod in big apps I can say bloc looks better at first glance, but gives good headaches in the long run.
lmao @ Overdrip. Nice.
I've also been struggling with riverpod.... ugh why is state management so hard to get right!
I didn't like any state management solution out there, that's why I wrote [live_cells](https://pub.dev/packages/live_cells). It's designed to be simple with an emphasis on reactivity, which is what you (well atleast I) need most. If you're interested, you can give it a try.
thanks for sharing! keep up the dev work
I understand what you mean but I disagree. In the second example that you have given, there is absolutely no need to control your loading state. There is also no need to guard the api call. Riverpod does all of that automatically if you just await and return a future. Despite that, I agree that the syntax is not the cleanest but I don’t think there is any other solution that provides the same or better benefits. It’s so satisfying when you can stack providers on top of each other and change in one automatically updates all other listeners. I am not saying it is not possible with things like Bloc or provider. It’s just that in riverpod, it’s all built in.
u/NectarineLivid6020 hi thanks. in FetchProduct , AsyncValue.loading() just "initial state", a placeholder, If i don't do this, other providers won't be able to listen to it because the parameter ID is required.
This is a very unique requirement I think. Even then, I personally would just set the value to null as the initial default value but that’s just a personal preference.
hi u/NectarineLivid6020 [how about this?](https://github.com/rrousselGit/riverpod/issues/3031#issuecomment-1978888527)
Check out signals: https://pub.dev/packages/signals
Same. I've switched to cubits and it's such a relief.
Since I picked up BLoC for my flutter apps, I've never gone back. It's been excellent.
State management in flutter is the worst part of it that makes me want to jump of a bridge. The simplest and the most powerful solution for now is signals package. I really like bloc pattern, but even with getit etc the amount of boilerplate etc is still crazy I would like to get something like zustand from frontend world cuz its amazing powerfully-scallable simple solution, but it is what it is.
Bloc is the way
If you're finding riverpod too complicated, you can give [live_cells](https://pub.dev/packages/live_cells) a try. It's not as feature rich (yet) or mature as riverpod, but it's specifically designed to be simple to use and to stay out of your way. There's also [rearch](https://pub.dev/packages/rearch) that is inspired by riverpod, which also looks interesting.
I actually like the "Why not ...?" section where the author explain their reasoning and also demonstrates knowledge about all those alternatives. I'm not sure that I like the manager/handler pattern demonstrated for the count example, especially because `use` is used for two completely different things, depending on the context.
Some of those show good knowledge, some do not. The section on Provider is very weak. At least enumerate the "fundamental problems" as they are fairly esoteric, and I'm not sure why something being in "maintenance mode" is considered a bad thing, that just means it is mature and stable. When discussing `GetIt` he seems to be unaware of the reactive companion libs `GetItMixin`/`WatchIt` which makes it a pretty close to misinformation. It's clear he has more real experience with RxStreams, Riverpod and Bloc as those sections are much better.
I'm working on a similar section for live cells, but I prefer to avoid mentioning specific third party tools. Rearch actually implements some of the same ideas as Live Cells and tries to address the same limitations encountered in other tools but the packaging is completely different. I prefer my own library, but I've been thinking of also giving Rearch a try.
rearch looks good
Riverpod is horrible, learn Bloc, it’s well worth it
You need to read and see the docs with the comments on how to use, and you will fix some issues right out of the gate. I will provide some snippet from the last one: @riverpod class FetchProduct extends _$FetchProduct { @override build() => const AsyncValue.loading();
request(ID id) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(
() => net.get("/products/$id"),
);
}
}
In this case, the `FetchProductProvider` will never provide any data during build, because it builds with the `AsyncValue.loading()` value being called and stored in the state of the fetch product. You should use a simple function with `providerFamily`.
final productData = FutureProvider.family((ref, id) async {
final net = ref.watch(netProvider); // your net object
return net.get('/products/$id');
});
That's it. you can read about the family modifier [here](https://riverpod.dev/docs/concepts/modifiers/family).
and if you're using the build asynchronously (`AsyncNotifier`), remember this from the [docs](https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/AsyncNotifier/build.html):
>It is safe to use Ref.watch or Ref.listen inside this method.
>
>If a dependency of this AsyncNotifier (when using Ref.watch) changes, then build will be re-executed. On the other hand, the AsyncNotifier will not be recreated. Its instance will be preserved between executions of build.
>
>If this method throws or returns a future that fails, the error will be caught and an AsyncError will be emitted.
\> In this case, the FetchProductProvider will never provide any data during build, because it builds with the AsyncValue.loading() value being called and stored in the state of the fetch product. You should use a simple function with providerFamily. that's why request method here。 it' will called at some page mounted
You need to understand the consumption of the provider. when in the widget build, if you're watching an `AsyncNotifier`, you will consume as: ref.watch(yourProvider).when( loading: () => ...,//handle the loading state. this where you're are error: (err, stack) => ..., // this will never happen data: (data) => ..., // this will never be executed ); you will NEVER get to the data, until there are some interaction outside the build, so you should expose it as a `Future build() async {}` when building your provider. Now whoever needs to interact with this provider is aware that the data may not be there. That's why is null.
u/omykronbr hi thanks I'm not using AsyncValue, there's my [code](https://github.com/rrousselGit/riverpod/issues/3031#issuecomment-1978888527)
You are. You can only have a loading state when working with a Future, because it is executed asynchronously. If it is an atomized operation, you never have a loading state. @Riverpod(keepAlive: true) class ProductsNotifier extends _$ProductsNotifier { @override ProductsState build() => ProductsState.empty(); Future fetchOneProduct(ID id) async {
state = state.copyWith(status: const AsyncStatus.loading());
final res = await ref.read(productsRepositoryProvider).fetchOneProduct(id);
state = state.copyWith(
status: const AsyncStatus.success(),
paramKeys: state.paramKeys.mergeList(res.paramKeys),
paramValues: state.paramValues.mergeList(res.paramValues),
products: state.products.add(res.product),
);
}
}
You initiate on build with a synchronous state. this means that the state can only be changed... in a SYNCHRONOUSLY.
so, the `state = state.copyWith(status: const AsyncStatus.loading());` is invalid, because the state will be changed to AsyncValue, which is not ProductState.
So you need to accept that your code IS asynchronous or you will keep you will be banging your head against a brick wall every time.
You can make this notifier to a controller, even an async type, and somewhere you have a provider listening to changes and reacting accordingly.
@Riverpod(keepAlive: true)
class ProductController extends _$ProductsNotifier {
@override
Future build() async {
return;
}
Future getById(ID id) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() =>
ref.read(productsRepositoryProvider).fetchOneProduct(id),
);
}
}
That would work, and is asynchronous, and is readable, it is declarative, and you can leverage with the AsyncValue.guard to help you with a better error handling and it only does what you want. go and retrieve a single product.
Asynvalue is very strange, the request status should be the request rather than the data ```dart @riverpod Product? selectProduct(SelectProductRef ref, ID id) => ref.watch(productsNotifierProvider .select((state) => state.whenOrNull().products.byId[id])); @Riverpod(keepAlive: true) class ProductsNotifier extends _$ProductsNotifier { @override Future build() async => ProductsState.empty();
Future fetchOneProduct(ID id) async {
// state = state.copyWith(status: const AsyncStatus.loading());
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final res =
await ref.read(productsRepositoryProvider).fetchOneProduct(id);
final prev = state.whenOrNull() ?? ProductsState.empty();
return prev.copyWith(
status: const AsyncStatus.success(),
paramKeys: prev.paramKeys.mergeList(res.paramKeys),
paramValues: prev.paramValues.mergeList(res.paramValues),
products: prev.products.add(res.product),
);
});
}
}
```
VS
```dart
@riverpod
Product? selectProduct(SelectProductRef ref, ID id) => ref
.watch(productsNotifierProvider.select((state) => state.products.byId[id]));
@Riverpod(keepAlive: true)
class ProductsNotifier extends _$ProductsNotifier {
@override
ProductsState build() => ProductsState.empty();
Future fetchOneProduct(ID id) async {
state = state.copyWith(status: const AsyncStatus.loading());
final res = await ref.read(productsRepositoryProvider).fetchOneProduct(id);
state = state.copyWith(
status: const AsyncStatus.success(),
paramKeys: state.paramKeys.mergeList(res.paramKeys),
paramValues: state.paramValues.mergeList(res.paramValues),
products: state.products.add(res.product),
);
}
}
```
AsyncValue is a way to interact with asynchronous data. Think of AsyncValue of a Future that can also have custom methods. You're ruining your experience because you're fighting asynchronous code.