Comments (43)
Future<int> fetch() aynsc => 42;
class Whatever extends StateNotifier<AsyncValue<int>> {
Whatever(): super(const AsyncValue.loading()) {
_fetch();
}
Future<void> _fetch() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() => fetch());
}
}
from riverpod.
Yes... But not for long π
from riverpod.
Well AsyncNotifier
is out, although undocumented.
So this problem should be solved
from riverpod.
The suggestion
Future<int> fetch() aynsc => 42; class Whatever extends StateNotifier<AsyncValue<int>> { Whatever(): super(const AsyncValue.loading()) { _fetch(); } Future<void> _fetch() async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() => fetch()); } }
works, and isn't super complicated, but it would be quite nice if a StateNotifier
that receives some async input could be set up to not have to deal with that async input itself.
For instance, let's say we have a
class TodoListManager extends StateNotifier<List<Todo>> {...}
And getting that initial List<Todo>
depends on something that is provided asynchronously, let's say we need to initialize a repository (or just fetch items from it asynchronously):
final repositoryProvider = FutureProvider<Repository>((ref) async {
final database = await ref.watch(databaseProvider.future);
final repository = getRepository(database);
return repository;
});
Now, I can have my TodoListManager extends StateNotifier<AsyncValue<List<Todo>>>
and have it perform whatever databaseProvider
does itself, like the suggestion. However, now my TodoListManager
will need to add conditions to all its operations to check if the data has been loaded. Also, if I want to write a test for my TodoListManager
, rather than just passing it a List<Todo>
, I now have to pass it a (mock) database. It would be a lot easier if I could wrap the whole thing in something that deals with the async loads, and from that point on just have a StateNotifier
that works with the loaded data. That way the StateNotifier
doesn't need to be explicitly responsible for where that data came from.
I guess a FutureStateNotifierProvider
is what I'm looking for, which would return an AsyncValue
for the state as well as for the provider.notifier
. I have to admit that I don't understand the fundamentals of this all well enough (yet) to know if this is even possible. It's also possible I'm completely missing the point somewhere, in which case I apologize.
For what it's worth, I'm currently working around this issue by creating a helper class with all the logic that I would like to have in my StateNotifier
, and using that helper class from the actual StateNotifier<AsyncValue<..>>
. This mitigates the testability and state check issues, because I can test the helper class separately, but it feels like hack.
from riverpod.
I was only advocating this method because you said you wanted to 1) get data from some async function and 2) store that data in a statenotifier without requiring the use of AsyncValue. What I posted would accomplish that, I think. I suggested using initState() of a stateful widget because that method fires only once upon widget creation. It doesn't necessarily have anything to do with that widget's state. You're just putting a side effect in some widget's initState()
Fair enough, it looks like I'm not doing a good enough job to clearly put in to words what I think is causing the confusion. So let me try to paint a scenario.
Let's say I'm working on a TODO list app. I have a page where TODOs are displayed, added and deleted, and a page that displays TODO statistics. Everything is set up using mock data for now. Here's the provider for the statistics page:
final statsProvider = Provider((ref) {
final todos = ref.watch(mockRepository).getTodos();
return Statistics(todos);
}
ref.watch(statsProvider); // Statistics
For the list/add/edit/delete page I have a TodoManager extends StateNotifier<List<Todo>>
. Here's its provider:
final todoManagerProvider = StateNotifierProvider<TodoManager, List<Todo>>((ref) {
final todos = ref.watch(mockRepository).getTodos();
return TodoManager(todos);
});
ref.watch(todoManagerProvider); // List<Todo>
ref.watch(todoManagerProvider.notifier); // TodoManager
The whole UI is set up and working fine, now I'm ready to hook up real data. I come to the conclusion that in order to load the TODOs, I need an async call. So, I swap out the statistics provider for a FutureProvider
:
final statsProvider = FutureProvider((ref) {
final todos = await ref.watch(mockRepository).getTodos();
return Statistics(todos);
}
ref.watch(statsProvider); // AsyncValue<Statistics>
Of course the UI will have to deal with the output being AsyncValue<Statistics>
, but I'm managing the dependencies in the same place, and everything else stays roughly the same. Now it's time for the StateNotifier
, and here's where I think people get confused. Based on the above, I'd say what people most likely expect is the following:
// THIS DOESN'T WORK
// ...for whoever is just skimming the code blocks
final todoManagerProvider = FutureStateNotifierProvider<TodoManager, List<Todo>>((ref) async {
final todos = await ref.watch(mockRepository).getTodos();
return TodoManager(todos);
});
ref.watch(todoManagerProvider); // AsyncValue<List<Todo>>
ref.watch(todoManagerProvider.notifier); // AsyncValue<TodoManager>
Again the UI will have to deal with those AsyncValue
s again, but otherwise everything stays the same.
Instead, the solution requires moving all async initialization into the TodoManager
, which can quite drastically change how that class behaves internally, because every add / delete / read operation needs to be aware of the loading status of the data. It requires a restructuring of the code on both ends. Now I haven't quite made up my mind on whether or not this is justified (there's maybe something fundamentally different about that last pretend code), but it certainly is unexpected. It may just be a matter of documentation. I'd be happy to help there by the way, I'm already producing pages of prose here anyway π.
All that being said
What I wanted for my actual code was for everything internally to stay the same, and for nested widgets to have easy access to an already loaded StateNotifier
through ref.watch()
. In my case that StateNotifier
is also part of a .family
, and I didn't really want to pass either that StateNotifier
or the .family
parameter that creates it down the tree. It was that last part that made me realize I could use a ProviderScope
to solve the issue. Applied to the previous, my solution now is:
final futureTodoManagerProvider = FutureProvider((ref) async {
final todos = await ref.watch(mockRepository).getTodos();
return TodoManager(todos);
});
final todoManagerProvider = StateNotifierProvider<TodoManager, List<Todo>>((ref) =>
throw UnimplementedError("Not provided"));
Widget someBuildMethod(BuildContext context, ref) {
final manager = ref.watch(futureTodoManagerProvider);
return manager.when(
error: (_, __) => Text('ERROR'),
loading: () => Text('LOADING'),
data: (data) => ProviderScope(
overrides: [todoManagerProvider.overrideWithValue(data)],
child: Container(/* Everything in here is the same as before */),
),
);
}
This does everything I need it to, including giving me the ability to simply ref.watch(todoManagerProvider)
anywhere in the nested widget tree, even when that original TodoManager
was part of a family.
from riverpod.
Hello @joanofdart I don't think it is needed any update. It is clear the usage of asyncValue with Remi's example. The use case I exposed later could be solved really easy using a FutureProvider for update logic.
from riverpod.
@ebelevics Consider callling getBaseData
inside your baseDataProvider
provider instead of a widget
from riverpod.
I remember there were mentions about possible undesirable effects of nesting
ProviderScope
but can't find the discussion at this moment.
@mcrio The primary practical downside I experience is that a provider that uses another provider that is overridden in a ProviderScope
needs to explicitly specify its dependencies
, or a runtime error will be thrown. Otherwise the solution has been working quite well for me.
...I'm absolutely going to find out what AsyncNotifier
is in the next few minutes, though.
from riverpod.
Is extending StateNotifier<AsyncValue> what you're looking for?
from riverpod.
Can you provide a simple example? I am quite confused about how it should be done. Thank you ππ»
from riverpod.
Oh I see!!! That should do it, yes. That really helped. Thank you so much for everything!
from riverpod.
But there's a case involved in this situation I cannot see.
We have the todo list fetched and saved correctly in our stateNotifier extending asyncValue. We decide now to edit some todos, calling a update method inside the stateNotifier class and proceed to post this new data to API to save this edited todos in a database but the API call fails to update this todos on server. And now the user, instead of trying again wants to cancel edition and retrieve latest state available.
In this case the provider's state is returning AsyncValue.error but since we canceled edition we want to see latest AsyncValue.data.
How should we proceed in this case? Maybe stateNotifier is not the best suited for this case? But we also want the data, loading, error management.
from riverpod.
You could override the setter of state
to internally keep track of the latest valid value
from riverpod.
Yes... But look like a difficult solution to something quite usual. Maybe would be better to create a FutureProvider apart to handle update logic.
from riverpod.
Hello π has there been any updates on this : D? just checking in since I'm really digging river_pod but I'm a bit dumb and having a hard time with async data (firebase-stuff).
from riverpod.
For simple
Future<int> fetch() aynsc => 42;
final Whatever = FutureProvider<int> async{
final result = await fetch();
return result;
}
and context.refresh(Whatever)
if you want to refresh it
from riverpod.
thank you both @tbm98 and @edmbn
May I ask you to take a look at this link? #104 I'm trying to learn riverpod and hopefully I'm going it the right way.
from riverpod.
I see. but in your use-case, I think you don't need to use StateNotifier, use FutureProvider is enough.
in #104 it has more than one behavior.
from riverpod.
Future<int> fetch() async => 42; class Whatever extends StateNotifier<AsyncValue<int>> { Whatever(): super(const AsyncValue.loading()) { _fetch(); } Future<void> _fetch() async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() => fetch()); } }
If the initialize async data is a custom object like:
Future<MyCustomModel> fetch() async {
return MyCustomModel(propA: 'hello', propB: 42, propC: true);
}
What's the code look like for a setter method? Is there some sort of copyWith
type of syntax to set a single property of the async state?
class Whatever extends StateNotifier<AsyncValue<MyCustomModel>> {
Future<void> setPropA(String newValue) async {
// what to do here?
// state = const AsyncValue.loading() - use this here as well?
}
Future<void> setPropB(int newValue) async {}
Future<void> setPropC(bool newValue) async {}
}
from riverpod.
@rrousselGit
I tried the above mentioned approach and locked at the performance analysis.
In both cases (use of Consumer and useProvider) there was a mentionable delay.
For my inexperienced eyes it looks as if the statenotifier blocks the widget build till the fetch method is run through. It would be awesome if you could reconfirm on your side that there is no problem. (best not to use my code as I simplified it quite a bit)
StateNotifier
final stateNotifierProvider =
StateNotifierProvider.autoDispose(
(ProviderReference ref) => ReaderStateNotifier());
class ReaderStateNotifier
extends StateNotifier<AsyncValue<KtList<User>>> {
ReaderStateNotifier()
: super(const AsyncValue<KtList<User>>.loading()) {
fetched();
}
Future<void> fetched() async {
state = const AsyncValue<KtList<User>>.loading();
final Either<ModelFailure, KtList<User>> result =
await userRepository.read();
state = result.fold((ModelFailure l) => AsyncValue<KtList<User>>.error(l),
(KtList<User> r) => AsyncValue<KtList<User>>.data(r));
}
}
Widget
Scaffold(
body: Consumer(builder: (context, watch, _) {
final state = watch(stateNotifierProvider.state);
return state.when(
data: (list) => ListView.builder(
shrinkWrap: true,
itemCount: list.size,
itemBuilder: (BuildContext context, int index) {
final User user = list[index];
return UserListTile(
user: user,
);
},
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, _) => Container(
color: Colors.red,
child: Text(
err.toString())), // TODO(jr): show real error handling)
);
}),
);
from riverpod.
For my inexperienced eyes it looks as if the statenotifier blocks the widget build till the fetch method is run through.
That isn't the case.
Could you provide a full example of how to reproduce this performance issue?
from riverpod.
@rrousselGit sorry that is took a while.
I first had to make sure that the issue is still present with the 0.14 version of riverpod.
To recreate the issue please, I created a separate repo, where you should be able to reproduce the error.
Furthermore here are the specific steps I took.
- restart the app (no hot reload, for obvious reasons π )
- in devTools clear the queue and have "Track Widget Builds" activated
- push the "Select Page" Button
- in devTools -> refresh
video
Peek.2021-04-21.17-48.mp4
from riverpod.
@jlnrrg Do you need the shrinkWrap = true? If I'm not mistaken, that can be slow for long lists.
A second thing, are you testing it profile mode? Emulators can't run profile builds.
from riverpod.
Thank you for providing these hints.
Regarding shrinkWrap = true
, this is an artifact from the real code, where the list gets actually quite large. But I now changed it in the example repo.
I was indeed not in profile mode, as I used the emulator for convenience.
But now I tried it on my phone in profile mode. And while the issue is less prone, it is still noticable.
from riverpod.
I just recently migrated project from Bloc(Cubits) to Riverpod(StateNotifier), and while everything works fine, I can't call Future API methods right just before pushing new page inside AppRoutes:
if (settings.name == HomeScreen.routeName) {
context.read(baseDataProvider.notifier).getBaseData();
return HomeScreen();
}
before it was
if (settings.name == HomeScreen.routeName)
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => BaseDataCubit(repository: sl<BaseRepository>())
..getBaseData(),
lazy: false,
),
],
child: HomeScreen(),
);
otherwise I get error
E/flutter (27384): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: Bad state: Tried to use BaseDataNotifier after `dispose` was called.
E/flutter (27384):
E/flutter (27384): Consider checking `mounted`.
In this case I call getBaseData() to load base data from API to use in further screens (ex. HomeScreen -> SettingsScreen -> BaseDataScreen). But I'm not really sure how to do it.
The only way I see right now is using context.read(baseDataProvider.notifier).getBaseData(); in HomeScreen initState(), but I would like to avoid StatefullWidgets as much as possible.
from riverpod.
@rrousselGit thank you for quick reply
I have moved getBaseData
inside BaseDataProvider
, but yet it still dropped the error about Consider checking mounted.
Then I wrapped my HomeScreen()
with Consumer
widget and now seems it did the job.
Now baseDataProvider
is initialized and tied with HomeScreen
, but now I can access it in deeper stacked route screens, without worrying now about not reaching right context to access state, as it was with BlocProvider. Thank you very much :)
if (settings.name == HomeScreen.routeName) {
return Consumer(
builder: (_, watch, __) {
watch(baseDataProvider);
return HomeScreen();
},
);
}
final baseDataProvider =
StateNotifierProvider.autoDispose<BaseDataNotifier, BaseDataState>(
(ref) {
final baseDataNotifier = BaseDataNotifier(repository: sl<BaseRepository>());
baseDataNotifier.getBaseData();
return baseDataNotifier;
},
);
class BaseDataNotifier extends StateNotifier<BaseDataState> {
...
I don't know is it the correct approach, but it does the initialization without errors.
P.S. I was also worried that sl() [get_it] would affect it, but it wasn't the case.
from riverpod.
However, now my TodoListManager will need to add conditions to all its operations to check if the data has been loaded.
If you really don't care about it, then just put it at the top level of your scaffold a la:
return Scaffold(
body: asyncProvider.when(error: (Object error, StackTrace? stackTrace) {
logger.warning(
"Error when trying to load asyncProvider: $error. \nStacktrace: $stackTrace");
}, loading: () {
const Text("Loading");
}, data: (data) {
return Text("Loaded stuff is $data")
});
If you aren't handling the situations where data is still loading then I feel like that's a fundamental mistake. Also, testing wouldn't be any different unless I'm missing something.
It would be a lot easier if I could wrap the whole thing in something that deals with the async loads, and from that point on just have a StateNotifier that works with the loaded data
Correct me if I'm wrong, but you can definitely do that, just have one of your higher level StatefulWidgets load it in initState()
by using yourAsyncFunction().then((data) => stateNotifierProvider.setStateFunction(data));
or something similar to that and then you're good to go. You would lose all of the functionality of being able to add loading or error widgets though
from riverpod.
If you aren't handling the situations where data is still loading then I feel like that's a fundamental mistake.
I want something to worry about the loading and error cases, just not that StateNotifier
.
Correct me if I'm wrong, but you can definitely do that, just have one of your higher level StatefulWidgets load it in initState() by using yourAsyncFunction().then((data) => stateNotifierProvider.setStateFunction(data)); or something similar to that and then you're good to go.
I don't think I quite understand what you mean here, are you suggesting setting the state of an already existing provider as an async callback in initState
, or creating a new provider in initState
? It seems like the former, in which case, sure, but the provider will still have to deal with potentially absent data (if state is being set it has already been created, after all), null
at the very least. It also doesn't really address the broader issue that we can't chain a StateNotifier
to something async without making the state async.
I hadn't thought about the latter, but it gave me an idea. Can I have a provider create another provider? I don't really understand this well enough under the hood to know if that would work just fine or subtly leak memory. This, for instance, seems to run as expected:
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Consumer(
builder: (context, ref, child) {
final asyncManagerProvider = ref.watch(loaderProvider);
return asyncManagerProvider.when(data: (data) {
final todos = ref.watch(data);
final manager = ref.watch(data.notifier);
return ElevatedButton(
onPressed: () => manager.add(),
child: Text('There are ${todos.length} todos.'),
);
}, error: (error, stack) {
return const Text('ERROR');
}, loading: () {
// Display loading
return const CircularProgressIndicator();
});
},
),
),
),
);
}
}
class Todo {}
class TodoManager extends StateNotifier<List<Todo>> {
TodoManager(List<Todo> todos) : super(todos);
void add() {
state = [
Todo(),
...state,
];
}
}
Future<List<Todo>> loadTodos() async {
await Future.delayed(const Duration(milliseconds: 2000));
return <Todo>[];
}
final loaderProvider = FutureProvider((ref) async {
final todos = await loadTodos();
final manager = TodoManager(todos);
return StateNotifierProvider<TodoManager, List<Todo>>((ref) => manager);
});
This is essentially what I'm looking for, the only minor qualm being that I'd like ref.watch(loaderProvider);
to return an AsyncValue
over List<Todo>
, and then having a ref.watch(loaderProvider.notifier)
like StateNotifierProvider
, which returns an AsyncValue<TodoManager>
. I suppose I could write that class, actually (you have no idea how many times I've already rewritten this comment after having another thought π
). I don't know if this could blow up in my face somehow, though.
from riverpod.
The whole point of the above, I should add, is that you can create a StateNotifier
from an async value and listen to it with ref.watch(...)
, without the StateNotifier
itself needing to know its input was async. Anything in the widget tree below the point where the notifier was instantiated can just ref.watch
the created provider directly if they have access to the resolved AsyncValue
, or retrieve the AsyncValue
and safely assume it has resolved to data.
from riverpod.
Okay one more before I stop polluting this issue, I completely forgot about ProviderScope
, but I feel like this is probably the best solution:
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Consumer(
builder: (context, outerRef, child) {
final asyncManagerProvider =
outerRef.watch(futureTodoManagerProvider);
return asyncManagerProvider.when(data: (data) {
return ProviderScope(
overrides: [todoManagerProvider.overrideWithValue(data)],
// Let's pretend we're nested deep in the widget tree
child: Consumer(
builder: (context, ref, child) {
final todos = ref.watch(todoManagerProvider);
final manager = ref.watch(todoManagerProvider.notifier);
return ElevatedButton(
onPressed: () => manager.add(),
child: Text('There are ${todos.length} todos.'),
);
},
),
);
}, error: (error, stack) {
return const Text('ERROR');
}, loading: () {
// Display loading
return const CircularProgressIndicator();
});
},
),
),
),
);
}
}
class Todo {}
class TodoManager extends StateNotifier<List<Todo>> {
TodoManager(List<Todo> todos) : super(todos);
void add() {
state = [
Todo(),
...state,
];
}
}
Future<List<Todo>> loadTodos() async {
await Future.delayed(const Duration(milliseconds: 2000));
return <Todo>[];
}
final futureTodoManagerProvider = FutureProvider((ref) async {
final todos = await loadTodos();
return TodoManager(todos);
});
final todoManagerProvider = StateNotifierProvider<
TodoManager,
List<
Todo>>((ref) => throw UnimplementedError(
"Access to a [TodoManager] should be provided through a [ProviderScope]."));
Load the manager in the outer scope that deals with the loading state, provide it to the inner scope through an "abstract" provider. No extra provider magic needed, listening is simpler too. This feels like a proper solution rather than a hack.
from riverpod.
I don't think I quite understand what you mean here, are you suggesting setting the state of an already existing provider as an async callback in initState, or creating a new provider in initState? It seems like the former, in which case, sure, but the provider will still have to deal with potentially absent data (if state is being set it has already been created, after all), null at the very least.
I was only advocating this method because you said you wanted to 1) get data from some async function and 2) store that data in a statenotifier without requiring the use of AsyncValue. What I posted would accomplish that, I think. I suggested using initState()
of a stateful widget because that method fires only once upon widget creation. It doesn't necessarily have anything to do with that widget's state. You're just putting a side effect in some widget's initState()
Ultimately, I think the correct way to go about this is doing what rousselGit suggested here because then you gain the added functionality of having your app be interactable while the async function is running in the background. I also think you're creating more work for yourself by having two providers, but if it works, it works. Don't you run into your same original issue of having to manager the loading, error, data
states in your app though?
from riverpod.
This is what I created to bypass this limitation, and with futures delayed to zero it works great.
Problem lies when I tried to call function without using Future (skipFuture enabled) which is needed in one page as in function I asign AsyncValue.loading to state at start. But I tried to get error and I can't anymore. It was something like (widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children,
).
but you can remove skipFuture from widget and call function from Future.delayed(Duration.zero,
and it just works.
class MyScopeProvider extends ConsumerStatefulWidget {
final Function(WidgetRef) call;
final bool skipFuture;
final Function(WidgetRef)? onDispose;
final Widget child;
const MyScopeProvider({required this.call, this.skipFuture = false, this.onDispose, required this.child, Key? key})
: super(key: key);
@override
_MyScopeProviderState createState() => _MyScopeProviderState();
}
class _MyScopeProviderState extends ConsumerState<MyScopeProvider> {
@override
void initState() {
if (widget.skipFuture) {
widget.call(ref);
} else {
Future.delayed(Duration.zero, () async => widget.call(ref));
}
super.initState();
}
@override
void dispose() {
if (widget.onDispose != null) widget.onDispose!(ref);
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
and call it with
MyScopeProvider(
call: (ref) => ref.read(someProvider.notifier).doSomeAsyncFunction(),
child: const SomeScreen(),
),
from riverpod.
Future<int> fetch() aynsc => 42; class Whatever extends StateNotifier<AsyncValue<int>> { Whatever(): super(const AsyncValue.loading()) { _fetch(); } Future<void> _fetch() async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() => fetch()); } }
Don't use 'const' on AsyncValue.loading(), It will make object the same and cannot be updated,
another way is override the updateShouldNotify.
from riverpod.
Future<int> fetch() aynsc => 42; class Whatever extends StateNotifier<AsyncValue<int>> { Whatever(): super(const AsyncValue.loading()) { _fetch(); } Future<void> _fetch() async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() => fetch()); } }
2 years later, is this still the recommended way to do it?
from riverpod.
@rrousselGit You expect some updates in the near future? The nested ProviderScope
solution mentioned by @ElteHupkes looks very clean. I remember there were mentions about possible undesirable effects of nesting ProviderScope
but can't find the discussion at this moment.
from riverpod.
@rrousselGit Thanks, AsyncNotifier
does the job.
from riverpod.
@rrousselGit Thanks,
AsyncNotifier
does the job.
Could you provide an example @mcrio ?
I'm using AutoDisposeAsyncNotifier
and awaiting a provider with await ref.watch(exampleProvider.future)
, but it throws the error mentioned in this issue: #1920
Example code that throws the error:
class ExampleNotifier extends AutoDisposeAsyncNotifier<ExampleData> {
@override
FutureOr<ExampleData> build() async {
final data = await ref.watch(exampleDataProvider.future);
final data2 = await ref.watch(exampleData2Provider.future);
return ExampleData(dataValue: data, data2Value: data2);
}
}
from riverpod.
@mgwrd Your code looks ok. Sorry no idea what might be wrong.
from riverpod.
ε₯½ε§
AsyncNotifier
οΌιηΆζ²ζθ¨ιγ ζδ»₯ιεει‘ζ該解決
AsyncNotifier is good, thanks.
from riverpod.
class PlaylistNotifier extends StateNotifier<AsyncValue<List<Song>>>
Future<void> prependSong(Song song) async {
...
state = AsyncValue.data([song, ...state.value!]);
}
Future<void> removeSong(int index) async {
...
state = AsyncValue.data(List.from(state.value!)..remove(song));
}
Is there a better way?
from riverpod.
You are currently meant to use AsyncNotifier instead of StateNotifier.
StateNotifier is a bit out of date
from riverpod.
Closing since AsyncNotifier should solve this.
There are separate issues for tracking better documentation of AsyncNotifier & redirecting the docs of StateNotifier to AsyncNotifier
from riverpod.
I was only advocating this method because you said you wanted to 1) get data from some async function and 2) store that data in a statenotifier without requiring the use of AsyncValue. What I posted would accomplish that, I think. I suggested using initState() of a stateful widget because that method fires only once upon widget creation. It doesn't necessarily have anything to do with that widget's state. You're just putting a side effect in some widget's initState()
Fair enough, it looks like I'm not doing a good enough job to clearly put in to words what I think is causing the confusion. So let me try to paint a scenario.
Let's say I'm working on a TODO list app. I have a page where TODOs are displayed, added and deleted, and a page that displays TODO statistics. Everything is set up using mock data for now. Here's the provider for the statistics page:
final statsProvider = Provider((ref) { final todos = ref.watch(mockRepository).getTodos(); return Statistics(todos); } ref.watch(statsProvider); // StatisticsFor the list/add/edit/delete page I have a
TodoManager extends StateNotifier<List<Todo>>
. Here's its provider:final todoManagerProvider = StateNotifierProvider<TodoManager, List<Todo>>((ref) { final todos = ref.watch(mockRepository).getTodos(); return TodoManager(todos); }); ref.watch(todoManagerProvider); // List<Todo> ref.watch(todoManagerProvider.notifier); // TodoManagerThe whole UI is set up and working fine, now I'm ready to hook up real data. I come to the conclusion that in order to load the TODOs, I need an async call. So, I swap out the statistics provider for a
FutureProvider
:final statsProvider = FutureProvider((ref) { final todos = await ref.watch(mockRepository).getTodos(); return Statistics(todos); } ref.watch(statsProvider); // AsyncValue<Statistics>Of course the UI will have to deal with the output being
AsyncValue<Statistics>
, but I'm managing the dependencies in the same place, and everything else stays roughly the same. Now it's time for theStateNotifier
, and here's where I think people get confused. Based on the above, I'd say what people most likely expect is the following:// THIS DOESN'T WORK // ...for whoever is just skimming the code blocks final todoManagerProvider = FutureStateNotifierProvider<TodoManager, List<Todo>>((ref) async { final todos = await ref.watch(mockRepository).getTodos(); return TodoManager(todos); }); ref.watch(todoManagerProvider); // AsyncValue<List<Todo>> ref.watch(todoManagerProvider.notifier); // AsyncValue<TodoManager>Again the UI will have to deal with those
AsyncValue
s again, but otherwise everything stays the same.Instead, the solution requires moving all async initialization into the
TodoManager
, which can quite drastically change how that class behaves internally, because every add / delete / read operation needs to be aware of the loading status of the data. It requires a restructuring of the code on both ends. Now I haven't quite made up my mind on whether or not this is justified (there's maybe something fundamentally different about that last pretend code), but it certainly is unexpected. It may just be a matter of documentation. I'd be happy to help there by the way, I'm already producing pages of prose here anyway π.All that being said
What I wanted for my actual code was for everything internally to stay the same, and for nested widgets to have easy access to an already loaded
StateNotifier
throughref.watch()
. In my case thatStateNotifier
is also part of a.family
, and I didn't really want to pass either thatStateNotifier
or the.family
parameter that creates it down the tree. It was that last part that made me realize I could use aProviderScope
to solve the issue. Applied to the previous, my solution now is:final futureTodoManagerProvider = FutureProvider((ref) async { final todos = await ref.watch(mockRepository).getTodos(); return TodoManager(todos); }); final todoManagerProvider = StateNotifierProvider<TodoManager, List<Todo>>((ref) => throw UnimplementedError("Not provided")); Widget someBuildMethod(BuildContext context, ref) { final manager = ref.watch(futureTodoManagerProvider); return manager.when( error: (_, __) => Text('ERROR'), loading: () => Text('LOADING'), data: (data) => ProviderScope( overrides: [todoManagerProvider.overrideWithValue(data)], child: Container(/* Everything in here is the same as before */), ), ); }This does everything I need it to, including giving me the ability to simply
ref.watch(todoManagerProvider)
anywhere in the nested widget tree, even when that originalTodoManager
was part of a family.
i think your solution is the best one fitted to my problem ,but when i am decided to use it new problem emerged the overrideWithValue(data) function is deleted so how to repair this issue
from riverpod.
Related Issues (20)
- black screen on the overview mode HOT 2
- Cannot use "ref" after the widget was disposed HOT 3
- (trivial) Examples using Bored API need a small update HOT 2
- Expose an interface for providers with notifiers HOT 2
- enable override the update condition of ref.watch HOT 3
- Why do you still generate _SystemHash in each file instead of using Object.hash/hashAll? HOT 5
- Assertion error when accessing provider with family dependencies in nested ProviderScope HOT 1
- Changing debouncing example to make more sense HOT 5
- Debouncing example app locks up HOT 11
- select doesn't work HOT 1
- I want to dynamically configure the keepAlive property in @Riverpod(keepAlive: true). How can I do this? HOT 1
- Unable to test build method of notifier provider if it changes during first camputation HOT 3
- What makes dynamically creating providers unsupported feature? HOT 1
- Documentation update HOT 3
- fireImmediately at AutodisposeStateNotifierProvider will crash if watched directly HOT 2
- Can't access/watch the AutoDisposeFamilyAsyncNotifierProvider HOT 1
- Bad state: Tried to read a provider from a ProviderContainer that was already disposed HOT 4
- Is it possible to do eager-initialisation of ProviderScopes? HOT 1
- examples/pub dependencies and auth problems HOT 1
- The subclass of AutoDisposeNotifier can always obtain internationalization, even if the phone's system Settings are Chinese, and finally English HOT 2
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.