Comments (18)
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 Provider<R>.value
using a named constructor.
from provider.
ProxyProvider2
would be syntax sugar for:
ProxyProvider<Foo, Bar>(
builder: (context, foo, previous) {
final baz = Provider.of<Baz>(context);
return Bar(foo, baz);
}
)
from provider.
Currently, providers can't depend on other providers. Not in a sane way at least.
Our "only" options are:
- Wrap the provider that depends on another provider into a
Consumer
. That works fine as long as the object is immutable and don't need to be disposed of – which is rare in theChangeNotifier
architecture. - use
Provider.of<Foo>(context, listen: false)
. Works fine if there's no update to do. It works with BLoC, but theChangeNotifier
architecture suffers again. - Make a custom
StatefulWidget
and handle everything ourselves. Tedious and error-prone.
Can we compose ProxyProvider
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 ProxyProvider
that consumes 1-6 values from other providers.
Similarly, I think it'd be interesting to have an optional providerBuilder
argument.
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 providerBuilder
would be
(context, value, child) => Provider.value(value: value, child: child),
from provider.
it is handled because of using previous in the builder like in the example you just mentioned?
Yes. That previous
exists for mutable objects. Immutable ones usually won't care about this argument.
from provider.
Ah, is this what you meant over at #44? I love this idea as it's a common use case and a clean API. From first glance, it doesn't look like there are perf implications.
I wonder what @jiaeric thinks.
from provider.
Ah, is this what you meant over at #44?
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:
- im/mutable objects
- updateShouldNotify
- combining it with
ChangeNotifierProvider
/similar - dispatching
setState
or similar within the value builder
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.
from provider.
Yeah, this could get pretty gnarly before things are satisfactorily answered. If you agree, I would put this outside 2.0.0 scope.
from provider.
I would put this outside 2.0.0 scope.
No problem! That's not a priority anyway, it's just fuel for discussions.
from provider.
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?
from provider.
I've tried to use ProxyProvider, but without providerBuilder
you have to ditch ChangeNotifierProvider
which I find easiest to use.
Is the following setup matching what you meant when suggested wrapping in Consumer?
Assuming that DataService is just to inject methods and it doesn't have any actual state in it.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider<DataService>.value(
value: DataService(NetworkService(), LocalService()),
child: Consumer<DataService>(
builder: (context, dataService, child) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
notifier: CountriesViewModel(dataService),
),
],
child: child,
);
},
child: ....
);
}
}
from provider.
What do you mean by:
but without providerBuilder you have to ditch ChangeNotifierProvider which I find easiest to use.
?
There's providerBuilder
in the current implementation available there: #59
And there's an example using it here
Assuming we go forward with ProxyProvider
, I'll add a few shorthands to make using ProxyProvider
with existing providers easier. Like ProxyChangeNotifierProvider
and such.
from provider.
Is the following setup matching what you meant when suggested wrapping in Consumer?
Yes but that still fails.
Assuming that DataService
is immutable, CountriesViewModel
still isn't.
Using your code snippet, you'll easily lose your state when rebuilding.
from provider.
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 withProxyProvider
it is handled because of using previous
in the builder like in the example you just mentioned?
And thank you for those explanations! You're the best!
from provider.
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 MultiProvider
as in #46 (comment).
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.
Maybe something like a ProxyConsumer
?
In any case, I like the direction Provider
is taking, thanks a lot!
from provider.
Also, it would be nice to simplify the 3 nested consumers I currently have.
There's a variation of Consumer
which consumes more than one value: Consumer2
, ..., Consumer6
ProxyProvider
would follow the same treatment. So in your example we may use ProxyProvider2
instead, which would listen to both AuthBase
and ValueNotifier<bool>
at the same time.
Otherwise SignInManager
isn't rebuilt when AuthBase
changes.
NIT if AuthBase
never changes, but it's cool to keep the providers reactive.
Is SignInManager
listening to the ValueNotifier<bool>
? If so, you're likely missing on a dispose
. Which means this usage of ProxyProvider
is likely invalid.
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 Provider<ValueNotifier<bool>>
into ListenableProvider<ValueNotifer<bool>>
.
Which means that SignInManager
do not need to listen to the ValueNotifier
, and may, therefore, become immutable.
Which also means that we can replace our previous example with:
ProxyProvider2<ValueNoftifier<bool>, AuthBase, SignInManager>(
builder: (_, isLoading, auth, _) => SignInManger(auth, isLoading.value),
)
from provider.
Do you have any example on how ProxyProvider2,3,4... should look?
from provider.
@rrousselGit thanks for all the info!
In my case SignInManager
doesn't listen to ValueNotifier
, only sets the value.
However you're right about disposing the ValueNotifier
. I should be doing this in the dispose
closure of Provider<ValueNotifier<bool>>
.
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 Consumer2
with types that have inter-dependencies. As in, SignInManager
takes ValueNotifier<bool>
as an input, but this is taken care of by the ProxyProvider
. So I can just list the types I need with ConsumerX
. Correct?
from provider.
Hey guys,
Something unclear about the docs. I am beginning to see that from the example the valueNotifier
in ProxyProvider
is coming from the Provider
above it. However the docs mention nothing about this and the variables aren't named the same, there's no indication that the constructor or object instance is shared. The parameter is passed in lower camel case but it's not clear where it's coming from. It's completely confusing. Can we update the docs to explain what's going on here?
from provider.
Related Issues (20)
- context.read CAN be called within build method HOT 1
- Trying to solve code duplication when working with lists of Widgets in Consumer HOT 4
- The shouldUpdate Selector method returns the same previous and next values HOT 1
- Listen to Changes from a Notifier HOT 3
- DevTools failed to connect with package:provider HOT 3
- Stream issue with StreamProvider HOT 3
- Parameter createdByTestHelpers no longer used in withIgnored method HOT 1
- Mitigate ProviderNotFoundException with context redirection HOT 7
- Null check operator used on a null value
- Make update() methods take a non-nullable previous HOT 5
- Cannot use parent class of provided class HOT 6
- Nested models cannot update UI HOT 1
- The values from the provider dev tools extension should be selectable & copyable
- postfix with notify listeners wont show value HOT 1
- Provider.shared for Efficient SharedInstance Management HOT 4
- `context.select` in `ProxyProvider0.update` not allowed? HOT 1
- How to use context.watch in `initState` HOT 2
- the provider instance cannot be obtained correctly in methodChannel.setMethodCallHandler HOT 1
- Why when creating a Consumer, an optional child is asked? When is it used? HOT 2
- How to auto reset the value of Provider ?? This is the ,measure issue i find on Provider, Can you please suggest me how to handle reset all value inside TestProvider without create method manually to reset any value??
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from provider.