Hello and thanks for reading!
This issue is about slightly changing the syntax of how provider variants are created.
Feel free to comment and share your opinion on the matter!
Currently, we have many classes, which are all basically the same modulo a small name + prototype change.
The current syntax is a permutation of:
| | | | |
|-------------|----------------|----------|--------|
| โ | State | Provider | โ |
| AutoDispose | ChangeNotifier | | Family |
| | Future | | |
| | Stream | | |
| | StateNotifier | | |
For a total of 24 variants.
With a secondary table for Computed.
But that's not very ideal:
AutoDisposeChangeNotifierProviderFamilyThe last point is especially important, as ideally, I would like to add new variants:
Future/Stream in a separate column, such that we could have a FutureStateProvider for exampleWhich means the table of variants would become:
| | | | | | |
|-------------|-------|--------|----------------|----------|--------|
| โ | โ | โ | โ | Provider | _ |
| AutoDispose | Retry | Future | StateNotifier | | Family |
| | | Stream | State | | |
| | | | ChangeNotifier | | |
That leads to a ridiculous 128 providers
The idea is to merge _all_ the variants into two public classes: Provider and Computed.
Riverpod would still have 128 providers internally, but they would be code-generated so it doesn't matter for end-users.
The way this new syntax would work is using the Builder design pattern.
Basically, instead of:
final provider = AutoDisposeChangeNotifierProviderFamily<MyNotifier, int>((ref, id) {
return MyNotifier();
});
We would have:
final provider = Provider
.changeNotifier
.autoDispose
.family<MyNotifier, int>((ref, id) {
return MyNotifier();
});
Whereas:
final provider = AutoDisposeProvider<Object>((ref) => Object());
would become:
final provider = Provider.autoDispose<Object>((ref) => Object());
And Provider((ref) => Object()) would be untouched.
The difference:
package:riverpod/providers.dart).stream and .autoDispose.Should some provider variants stay as is, such that we would still have StateProvider(...) instead of Provider.state(...)?
If so, what are the providers that we want to keep this way? I'm thinking of:
And then for fancier versions like FutureStateNotifierProvider would become StateNotifierProvider.future.
I agree with the proposal.
I think that having too many different class names can be confusing for people trying out the package.
I also agree that keeping some of the providers with the original name can be helpful for people used to the well known Providers in the provider package.
For my current project I started to do
final myProvider = createProvider.stateNotifier.family<T, Fam>((r, f) => .. );
by just using callable classes :)
I've just realised that we can't implement Provider.changeNotifier(...) as ChangeNotifier is Flutter-only, and extensions do not allow adding static members.
So this reinforces the idea of keeping StateNotifierProvider & co, and apply this logic only to AutoDispose, Family and a potential Retry.
Personally i would prefer Retry over anything else :P
Another alternative is to do like @smiLLe suggested and go for final foo = provider.autoDispose(...) instead of final foo = Provider.autoDispose(...).
This will allow using extensions to add custom members. And to begin with, we don't care about const constructors, so this using a function over a constructor doesn't matter.
I'll go for StateNotifierProvider.autoDispose.family(...).
This avoids a name clash with variables named provider, and is more natural for people migrating from provider
It'll then be accompanied by:
.retry, as per #42 .restorable, for state restoration:dart
final model = StateNotifierProvider.future.restorable(
(ref) async {
final json = await ref.restore();
final initialState = json == null ? null : MyModel.fromJson(json);
return MyNotifier(initialState: initialState);
},
name: 'model',
serialize: (state) => state.toJson(),
);
I like how composable the modifier idea is. It allows implementing commonly wanted features almost without doing anything.
Do you have any modifiers in mind that I haven't mentioned yet?
Currently we'd have:
What other big feature would we want? ๐ค
@rrousselGit Some kind of logging mechanism? Not sure if that is possible, just sharing the idea ๐
Logging in implemented directly on ProviderScope
ProviderScope(
observers: [
LoggerObserver(),
],
child: MyApp(),
);
class LoggerObserver extends ProviderStateOwnerObserver {
...
}
Although maybe we could have an "onChange" on providers individually
What other big feature would we want? ๐ค
Do you know recoil js? It is very similar to riverpod :) Maybe we can take some inspiration from them.
But right now, all i am missing is Retry and i like the idea of .restorable
I do. Finding Recoil made me rename Family. Originally the pattern was called "ParametrizedProvider", which just sounds bad
Anyway, we'll see later. I'll focus on retry/restorable. There's a bunch of work to do on for the provider code-generator
I would agree that the change in name convention makes a great deal of sense here.
Most helpful comment
I'll go for
StateNotifierProvider.autoDispose.family(...).This avoids a name clash with variables named
provider, and is more natural for people migrating fromproviderIt'll then be accompanied by:
.retry, as per #42.restorable, for state restoration:dart final model = StateNotifierProvider.future.restorable( (ref) async { final json = await ref.restore(); final initialState = json == null ? null : MyModel.fromJson(json); return MyNotifier(initialState: initialState); }, name: 'model', serialize: (state) => state.toJson(), );