riverpod icon indicating copy to clipboard operation
riverpod copied to clipboard

Automatically scope families along with a parameterProvider

Open TimWhiting opened this issue 2 years ago • 10 comments

Transferred from another issue.

I feel like families have a common use case of creating another provider for the parameter and then overriding it -- effectively creating a scoped provider for a subbranch of the widget tree when multiple subtrees are alive at the same time and require a different instance of the same provider. Could that pattern be made easier?

final paramProvider = StateProvider<String>((ref) => '');
final myFamilyProvider = Provider.family<Something, String>(
    (ref, id) => Something(id), parameterProvider: paramProvider
);

ProviderContainer(
  overrides: [ 
    // This override will automatically overrides myFamilyProvide:
    paramProvider.overrideWith('something'),
    // (in Provider container this is effectively what happens)
    // myFamilyProvider.overrideWith((param) => 
    //          Provider((ref) => myFamilyProvider._create(ref, param));
  ],
);
// Desugars to ref.watch(myFamilyProvider(ref.watch(myFamilyProvider.paramProvider)));
ref.watch(myFamilyProvider.scoped) 

Alternatively you can require that the providers are more strongly linked:

final myFamilyProvider = Provider.family.scoped<Something, String, Provider<String>>(
   (ref, id) => Something(id), parameterProvider: (ref) => ''
);

ProviderContainer(
  overrides: [ 
    // This override will automatically overrides myFamilyProvide:
    myFamilyProvider.param.overrideWith('something'),
    // (in Provider container this is effectively what happens)
    // myFamilyProvider.overrideWith((param) => Provider((ref) => myFamilyProvider._create(ref, param));
  ],
);
// Desugars to ref.watch(myFamilyProvider(ref.watch(myFamilyProvider.paramProvider)));
ref.watch(myFamilyProvider.scoped) 

You still have to override all of the dependencies of myFamilyProvider if you want those to be unique in the subtree as well. But at least this common case of using families is easier.

I was playing around with the riverpod code and it seems like we could make it work. ProviderBase just has an extra nullable field that is the family provider that is using it for scoping, and then in ProviderContainer when you override the param provider you check if that field is set and scope the family at that point.

TimWhiting avatar Sep 25 '21 02:09 TimWhiting

I think it would be nice to extend this issue to other things beyond just families, but I'm having a hard time figuring out what that would look like since the relationships between providers are more of a unidirectional graph and not a tree. I know we have talked about different methods of scoping providers to subtrees, but it seems rather difficult to do it in practice without specifying all of the overrides or where the providers need to be scoped in the widget tree just like with package:provider. The advantage over package:provider is still that you can provide multiple things of the same type and don't have as much noise in the widget tree in general, but the scoping story is a bit harder than package:provider since you have to override all providers that need replicating in the widget tree or you have to use families which require you to create a pass down an ID or piece of state for the parameter or create another provider for the parameter and overriding that, it also doesn't scale as well for families of families which you can get around by encapsulating multiple parameters in a class. This issue would reduce the noise of the parameters for families, but doesn't address all issues.

TimWhiting avatar Sep 28 '21 18:09 TimWhiting

The issue of overrides will be taken care of for the v1.

I do plan on adding the Provider(..., dependencies: {anotherProvider}) which would fix this. And the boilerplate would be fixed though an optional code generator, as we've discussed.

So in that sense, I think the problem you're mentioned is/will be solved.

rrousselGit avatar Sep 28 '21 19:09 rrousselGit

@rrousselGit Has this been made?

CodingSoot avatar Dec 08 '21 21:12 CodingSoot

It has, but not the family specific solution syntax. There is a dependencies parameter on all types of providers now.

TimWhiting avatar Dec 08 '21 21:12 TimWhiting

@TimWhiting Could you please explain the usage of the "dependencies" parameter ? I can't find any documentation about it.

CodingSoot avatar Dec 08 '21 21:12 CodingSoot

If you have:

final provider1 = Provider((ref) => ref.watch(provider2), dependencies: [provider2]);
final provider2 = Provider((ref) => '', dependencies: [provider2]);

and you scope provider2 then provider1 will also be scoped to that part of the widget tree.

TimWhiting avatar Dec 08 '21 21:12 TimWhiting

Thanks !

I feel like families have a common use case of creating another provider for the parameter and then overriding it -- effectively creating a scoped provider for a subbranch of the widget tree when multiple subtrees are alive at the same time and require a different instance of the same provider. Could that pattern be made easier?

Does using this new "dependencies" parameter make this pattern easier in any way?

CodingSoot avatar Dec 08 '21 22:12 CodingSoot

Yes it does if you use this pattern a lot. I'm not currently using dependencies a lot for this particular use case, but another one very similar to it.

TimWhiting avatar Dec 08 '21 22:12 TimWhiting

dependencies is available, so I'll close this.

I'm aware that dependencies isn't perfect. But there are a few upcoming things that will significantly improve it. So I think this can be closed.

rrousselGit avatar Jul 31 '22 13:07 rrousselGit

Adding reference to latest syntax proposal from Remi: https://github.com/rrousselGit/riverpod/issues/2353#issuecomment-1494148804

@riverpod
Model fn(FnRef ref, {required int userId, required int bookId}) {...}

ProviderScope(
  overrides: [
    fnProvider.withDefault(userId: 42, bookId: 21),
  ],
)

Related: #2356

TimWhiting avatar Apr 08 '23 01:04 TimWhiting