-
-
Notifications
You must be signed in to change notification settings - Fork 512
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC] Simplifying providers that depends on each others #46
Comments
One of the possible implementations could be: class ProxyProvider<T, R> extends StatefulWidget
implements SingleChildCloneableWidget {
const ProxyProvider({
Key key,
this.builder,
this.updateShouldNotify,
this.child,
}) : super(key: key);
final R Function(BuildContext, T, R) builder;
final UpdateShouldNotify<R> updateShouldNotify;
final Widget child;
@override
_ProxyProviderState<T, R> createState() => _ProxyProviderState();
@override
ProxyProvider<T, R> cloneWithChild(Widget child) {
return ProxyProvider(
key: key,
builder: builder,
updateShouldNotify: updateShouldNotify,
child: child,
);
}
}
class _ProxyProviderState<T, R> extends State<ProxyProvider<T, R>> {
R value;
@override
void didChangeDependencies() {
super.didChangeDependencies();
value = widget.builder(context, Provider.of<T>(context), value);
}
@override
Widget build(BuildContext context) {
return Provider<R>.value(
value: value,
child: widget.child,
updateShouldNotify: widget.updateShouldNotify,
);
}
} We may want to give the ability to use a provider other than |
It's related, but not exactly. My remark on #44 got me thinking about potential use-cases/flaws, and it here we are. 😄 I think that's an interesting idea. But I want to make sure that we don't solve an issue by introducing a bigger problem down the road. The current example is relatively easy. I'll think of more complex scenarios with a combination of:
There are some questions that need to be answered first I think. Like "when do we dispose of the created value?" because some objects may be recreated while others may be mutated. |
Yeah, this could get pretty gnarly before things are satisfactorily answered. If you agree, I would put this outside 2.0.0 scope. |
No problem! That's not a priority anyway, it's just fuel for discussions. |
Coming back to this: what's the implication when a provider depends on multiple other providers? I don't have a use case at hand, but I can imagine it will crop up. Can we compose ProxyProviders inside each other? |
Currently, providers can't depend on other providers. Not in a sane way at least. Our "only" options are:
Definitely! MultiProvider(
providers: [
Provider<Foo>(...),
ProxyProvider<Foo, Bar>(
builder: (context, foo, previous) => Bar(foo),
),
ProxyProvider<Bar, Baz>(
builder: (context, bar, previous) => Baz(bar),
),
]
); I assume we'll want variations of the Similarly, I think it'd be interesting to have an optional ProxyProvider<Foo, Bar>(
builder: (context, foo, previous) => Bar(foo),
providerBuilder: (context, bar, child) => ChangeNotifier.value(value: bar, child: child),
child: <whatever>
) and the default behavior of (context, value, child) => Provider.value(value: value, child: child), |
I've tried to use ProxyProvider, but without Is the following setup matching what you meant when suggested wrapping in Consumer?
|
What do you mean by:
? There's And there's an example using it here Assuming we go forward with |
Yes but that still fails. Using your code snippet, you'll easily lose your state when rebuilding. |
Oh, I don't know how I missed it... Sorry for that... Yeah, you are right about losing state... So do I understand correctly that with And thank you for those explanations! You're the best! |
Yes. That |
After this change, we’re managing Counter’s lifecycle with ChangeNotifierProvider. This removes the periodic Timer. Not only does that simplify the example and makes it closer to the original, it also prevents a leaking timer (though, in this case, it’s not an issue, since the timer is needed for the whole duration of the app). I experimented with a more robust approach (having or injecting a Timer/StreamController into the Counter, and disposing of it there) but that seemed overly complex for such a simple example. This whole problem will get significantly easier with rrousselGit/provider#46, at which point I could reintroduce this. I will also think about a more complex Provider example, something like the venerable `bloc_complex`, with infinite-scrolling pagination and all that.
I've been playing with providers with inter-dependencies. One example I currently have looks like this: static Widget create(BuildContext context) {
final auth = Provider.of<AuthBase>(context);
return Provider<ValueNotifier<bool>>(
builder: (context) => ValueNotifier<bool>(false),
child: Consumer<ValueNotifier<bool>>(
builder: (context, valueNotifier, _) => Provider<SignInManager>(
builder: (context) =>
SignInManager(auth: auth, isLoading: valueNotifier),
child: Consumer<SignInManager>(
builder: (context, manager, _) => ValueListenableBuilder<bool>(
valueListenable: valueNotifier,
builder: (context, isLoading, _) => SignInPage(
manager: manager,
isLoading: isLoading,
),
),
),
),
),
);
} This could be reduced to: static Widget create(BuildContext context) {
final auth = Provider.of<AuthBase>(context);
return Provider<ValueNotifier<bool>>(
builder: (context) => ValueNotifier<bool>(false),
child: ProxyProvider<ValueNotifier<bool>, SignInManager>(
builder: (context, valueNotifier, _) =>
SignInManager(auth: auth, isLoading: valueNotifier),
child: Consumer<SignInManager>(
builder: (context, manager, _) => ValueListenableBuilder<bool>(
valueListenable: valueNotifier,
builder: (context, isLoading, _) => SignInPage(
manager: manager,
isLoading: isLoading,
),
),
),
),
);
} Beyond that, it would be nice to use After some experimentation, I ended up with something like this: static Widget create(BuildContext context) {
final auth = Provider.of<AuthBase>(context);
return MultiProvider(
providers: [
Provider<ValueNotifier<bool>>(
builder: (_) => ValueNotifier<bool>(false)),
ProxyProvider<ValueNotifier<bool>, SignInManager>(
builder: (_, valueNotifier, _) =>
SignInManager(auth: auth, isLoading: valueNotifier),
),
],
child: Consumer<ValueNotifier<bool>>(
builder: (_, valueNotifier, _) => Consumer<SignInManager>(
builder: (_, manager, _) => ValueListenableBuilder<bool>(
valueListenable: valueNotifier,
builder: (_, isLoading, _) => SignInPage(
manager: manager,
isLoading: isLoading,
),
),
),
),
);
} Does this look reasonable? Also, it would be nice to simplify the 3 nested consumers I currently have. In any case, I like the direction |
There's a variation of
Is All of these combined, you may want that instead: ProxyProvider2<ValueNoftifier<bool>, AuthBase, SignInManager>(
builder: (_, isLoading, auth, previous) {
final manager = previous ?? SignInManger();
manager.auth = auth;
manager.isLoading = isLoading;
return manager;
},
dispose: (_, manager) => manager.dispose(),
) Alternatively can change Which means that Which also means that we can replace our previous example with: ProxyProvider2<ValueNoftifier<bool>, AuthBase, SignInManager>(
builder: (_, isLoading, auth, _) => SignInManger(auth, isLoading.value),
) |
Do you have any example on how ProxyProvider2,3,4... should look? |
ProxyProvider<Foo, Bar>(
builder: (context, foo, previous) {
final baz = Provider.of<Baz>(context);
return Bar(foo, baz);
}
) |
@rrousselGit thanks for all the info! In my case However you're right about disposing the So this is what I came up with: return MultiProvider(
providers: [
Provider<ValueNotifier<bool>>(
builder: (_) => ValueNotifier<bool>(false),
dispose: (_, valueNotifier) => valueNotifier.dispose(),
),
ProxyProvider<ValueNotifier<bool>, SignInManager>(
builder: (_, valueNotifier, __) => SignInManager(auth: auth, isLoading: valueNotifier),
),
],
child: Consumer2<ValueNotifier<bool>, SignInManager>(
builder: (_, valueNotifier, manager, __) => ValueListenableBuilder<bool>(
valueListenable: valueNotifier,
builder: (_, isLoading, __) => SignInPage._(
manager: manager,
isLoading: isLoading,
),
),
),
); I really like that I can use |
After this change, we’re managing Counter’s lifecycle with ChangeNotifierProvider. This removes the periodic Timer. Not only does that simplify the example and makes it closer to the original, it also prevents a leaking timer (though, in this case, it’s not an issue, since the timer is needed for the whole duration of the app). I experimented with a more robust approach (having or injecting a Timer/StreamController into the Counter, and disposing of it there) but that seemed overly complex for such a simple example. This whole problem will get significantly easier with rrousselGit/provider#46, at which point I could reintroduce this. I will also think about a more complex Provider example, something like the venerable `bloc_complex`, with infinite-scrolling pagination and all that.
After this change, we’re managing Counter’s lifecycle with ChangeNotifierProvider. This removes the periodic Timer. Not only does that simplify the example and makes it closer to the original, it also prevents a leaking timer (though, in this case, it’s not an issue, since the timer is needed for the whole duration of the app). I experimented with a more robust approach (having or injecting a Timer/StreamController into the Counter, and disposing of it there) but that seemed overly complex for such a simple example. This whole problem will get significantly easier with rrousselGit/provider#46, at which point I could reintroduce this. I will also think about a more complex Provider example, something like the venerable `bloc_complex`, with infinite-scrolling pagination and all that.
Hey guys, Something unclear about the docs. I am beginning to see that from the example the |
After this change, we’re managing Counter’s lifecycle with ChangeNotifierProvider. This removes the periodic Timer. Not only does that simplify the example and makes it closer to the original, it also prevents a leaking timer (though, in this case, it’s not an issue, since the timer is needed for the whole duration of the app). I experimented with a more robust approach (having or injecting a Timer/StreamController into the Counter, and disposing of it there) but that seemed overly complex for such a simple example. This whole problem will get significantly easier with rrousselGit/provider#46, at which point I could reintroduce this. I will also think about a more complex Provider example, something like the venerable `bloc_complex`, with infinite-scrolling pagination and all that.
From experience looking at larger Flutter applications, it is relatively common to have some dependency between providers.
For example, we may have a
Provider<Configuration>
and aProvider<AuthentificationService>
where the latter depends on the former.But that's currently not something we can represent purely using
provider
package. Our only solution right now is to somehow ejectprovider
and handle everything ourselves like so:That's not good.
It reintroduces the ability to make a circular object graph & forget to update/dispose of an object.
We're loosing all the fancy securities that using widgets for DI brings.
The discussion on #44 made me realize that an advanced
Consumer
may be able to solve this issue.Naively we could think of doing:
... The problem is that we're doing
final bar = Bar(foo);
inside the build method ofConsumer
.That's not good either – we may have memory leaks.
But that's something we can fix using a more advanced
Consumer
, which I'll callProxyProvider
for now.The idea behind
ProxyProvider
is that it would work similarly to the default constructor ofProvider
, but also likeConsumer
as it would read values from other providers and pass them to thebuilder
.The same example using
ProxyProvider
would be:In that example, the
builder
ofProxyProvider
would be called on the first build; or wheneverFoo
changes.The text was updated successfully, but these errors were encountered: