rrousselGit / riverpod

A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
https://riverpod.dev
MIT License
6.28k stars 955 forks source link

Expose `refresh(Provider)` to `Provider`s via `ProviderReference` #79

Closed samandmoore closed 4 years ago

samandmoore commented 4 years ago

Is your feature request related to a problem? Please describe. I'm working on an app that uses riverpod and I'm finding that it's annoying and limiting that the only place where I can mark a Provider as needing to be refreshed / triggering it to be refreshed is from within the widget tree. In other words, I have to be somewhere that has a BuildContext in scope so that I can do context.refresh(someProvider).

For context, I'm building an app for storing recipes. One of the things you can do is delete a recipe. I am managing the lifecycle of deleting a recipe with a StateNotifier called DeleteRecipeNotifier. The notifier is pretty trivial. It roughly looks like this:

class DeleteRecipeNotifier extends StateNotifier<AsyncValue<bool>> {
  final Api api;

  DeleteRecipeNotifier({@required this.api}) : super(AsyncValue.data(false));

  Future<void> deleteRecipe(String id) async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      await api.deleteRecipe(id);
      return true;
    });
  }
}

This is paired with some UI for showing a bottom sheet with a confirm button and a loading indicator while the delete is happening. When the delete is successful, the bottom sheet is closed and the UI pops back to a different screen.

(forgive me for blending hooks and non-hooks here 😄 i'm exploring some different approaches)

class _DeleteSheet extends HookWidget {
  final Recipe recipe;

  const _DeleteSheet({
    Key key,
    @required this.recipe,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ProviderListener(
      onChange: (AsyncValue<bool> result) {
        if (result.data?.value ?? false) {
          context.refresh(recipesProvider);
          // close the bottom sheet and also pop back out of the current "view recipe screen"
          Nav.of(context)..pop()..pop();
        }
      },
      provider: deleteRecipeProvider.state,
      child: useProvider(deleteRecipeProvider.state).when(
        data: (_) => _ConfirmDelete(recipe: recipe),
        loading: () => Center(child: CircularProgressIndicator()),
        error: (e, __) => Center(child: Text(e.toString())),
      ),
    );
  }
}

Describe the solution you'd like What I'd love to be able to do is trigger a refresh of the recipesProvider from within my DeleteRecipeNotifier. Either by constructing it with a ProviderReference or passing in a lambda like refreshRecipes: () => ref.refresh(recipesProvider) to keep ProviderReference out of the notifier.

Describe alternatives you've considered Alternatively, I can keep doing what I'm doing in the example code above: listen to the notifier and trigger a refresh from the widget tree. If you've got a strong reason why I should prefer this approach, I'd love to hear more! I'm sure you have a philosophy around how this stuff should be done. I'm open to all sorts of ideas, and I totally accept that I might be thinking about this wrong.

Thanks!

smiLLe commented 4 years ago

There is a ref.container.refresh(provider);

samandmoore commented 4 years ago

There is a ref.container.refresh(provider);

@smiLLe oh jeeze 🤦🏻 I dunno how I missed that. thank you.

I guess then my questions are slightly different:

rrousselGit commented 4 years ago

Yes that's fine.

samandmoore commented 4 years ago

Awesome. Thanks a lot. And thanks for the library 😄