rrousselGit / state_notifier

ValueNotifier, but outside Flutter and with some extra perks
MIT License
311 stars 28 forks source link

Add StateNotifierProxyProvider #74

Closed andcea closed 1 year ago

andcea commented 1 year ago

Is your feature request related to a problem? Please describe. I'd like to be able to create a state notifier (using provider) based on external dependencies that can change.

Describe the solution you'd like An API similar to ProxyProvider or ChangeNotifierProxyProvider would be great. For example:

StateNotifierProxyProvider<MyStateNotifier, State>(
  update: (context, previous) => MyStateNotifier(
    context.watch<DependencyA>(),
    context.watch<DependencyB>(),
  ), // Or use `previous` if stateful
)

Describe alternatives you've considered

ProxyProvider0(
  update: (context, previous) => MyStateNotifier(
    context.watch<DependencyA>(),
    context.watch<DependencyB>(),
  ),  // Or use `previous` if stateful
  builder: (context, child) {
    final notifier = context.watch<MyStateNotifier>();
    return StateNotifierProvider.value(value: value);
  },
)

I'm not sure how correct this is. Given that providing the notifier is lazy, I think if we first request the notifier from a context that doesn't have watch access (i.e. onTap), then final notifier = context.watch<MyStateNotifier>() will throw. It also means that the state will be provided eagerly (once the notifier is requested).

Something better might be:

InheritedProvider<MyStateNotifier>(
  create: (context) =>  MyStateNotifier(
    context.read<DependencyA>(),
    context.read<DependencyB>(),
  ), // Or possibly be able to skip `create`
  update: (context, previous) => MyStateNotifier(
    context.watch<DependencyA>(),
    context.watch<DependencyB>(),
  ),  // Or use `previous` if stateful
  dispose: (_, controller) => controller.dispose(),
  child: DeferredInheritedProvider<MyStateNotifier, State>(
    lazy: lazy,
    update: (context, _) => context.watch<MyStateNotifier>(),
    startListening: (context, setState, notifier, _) {
      return notifier.addListener(setState);
    },
    child: child,
  ),
)

This has the advantage that providing both the notifier and state is lazy however, DeferredInheritedProvider doesn't currently take an update parameter (only create).

I'm not sure if there are any other workarounds for this solution.

rrousselGit commented 1 year ago

ProxyProviders was IMO a terrible idea.
The API is poorly designed and doesn't quite solve the problem.

I do not want to implement a new one.

I'd suggest using pkg:riverpod instead anyway. It solves object composition in a much better way.

andcea commented 1 year ago

@rrousselGit can you please then suggest a workaround or an opinion on the ones suggested above?

Moving to riverpod is not an option for everyone.

Also, why do you say ProxyProviders was a terrible idea?

rrousselGit commented 1 year ago

You could use StateNotifier.addListener to combine two statenotifiers.

ProxyProviders aren't very intuitive, both lots of duplicate + possible mistakes, and lack flexibility. Your state could get destroyed too often with no easy way to fix that.