-
-
Notifications
You must be signed in to change notification settings - Fork 515
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
Selective state recreation #764
Comments
Hello! I don't think it makes sense to use Using |
Hi, I am not sure if I can follow what you mean. Of course I could create my object inside of |
You wouldn't need the mixin, because you could make a widget instead You can create a reusable StatefulWidget which returns a provider and does the dependency tracking. |
I see, that makes sense to me. |
I have created a SelectiveProvidertypedef SelectiveProviderBuilder0<R> = R Function(BuildContext context);
typedef SelectiveProviderBuilder<T, R> = R Function(
BuildContext context,
T value,
);
typedef SelectiveProviderBuilder2<T, T2, R> = R Function(
BuildContext context,
T value,
T2 value2,
);
typedef SelectiveProviderBuilder3<T, T2, T3, R> = R Function(
BuildContext context,
T value,
T2 value2,
T3 value3,
);
typedef SelectiveProviderBuilder4<T, T2, T3, T4, R> = R Function(
BuildContext context,
T value,
T2 value2,
T3 value3,
T4 value4,
);
typedef SelectiveProviderBuilder5<T, T2, T3, T4, T5, R> = R Function(
BuildContext context,
T value,
T2 value2,
T3 value3,
T4 value4,
T5 value5,
);
typedef SelectiveProviderBuilder6<T, T2, T3, T4, T5, T6, R> = R Function(
BuildContext context,
T value,
T2 value2,
T3 value3,
T4 value4,
T5 value5,
T6 value6,
);
typedef SelectiveBuilder = List<dynamic>? Function(BuildContext context);
class SelectiveProvider0<R> extends SingleChildStatefulWidget {
final Widget? child;
final TransitionBuilder? builder;
final SelectiveProviderBuilder0<R> create;
final Dispose<R>? dispose;
final SelectiveProviderBuilder0<List<dynamic>>? selector;
final bool? notifier;
const SelectiveProvider0({
super.key,
this.child,
this.builder,
required this.create,
this.dispose,
this.selector,
this.notifier,
}) : super(child: child);
@override
State<SelectiveProvider0<R>> createState() => _SelectiveProvider0State<R>();
}
class _SelectiveProvider0State<R>
extends SingleChildState<SelectiveProvider0<R>> {
List<dynamic>? dependencies;
R? value;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final List<dynamic>? conditions = widget.selector?.call(context);
final List<dynamic> values = [if (conditions != null) conditions];
if (!const DeepCollectionEquality().equals(dependencies, values)) {
if (value != null) {
widget.dispose?.call(context, value as R);
}
value = widget.create(context);
dependencies = values;
}
}
@override
void dispose() {
widget.dispose?.call(context, value as R);
super.dispose();
}
@override
Widget buildWithChild(BuildContext context, Widget? child) {
assert(
widget.builder != null || child != null,
'$runtimeType used outside of MultiProvider must specify a child',
);
return Provider.value(
value: value as R,
child: widget.builder != null
? Builder(
builder: (context) => widget.builder!(context, child),
)
: child!,
);
}
}
class SelectiveProvider<T, R> extends SelectiveProvider0<R> {
SelectiveProvider({
super.key,
super.child,
super.builder,
required SelectiveProviderBuilder<T, R> create,
super.dispose,
SelectiveProviderBuilder<T, List<dynamic>>? selector,
}) : super(
create: (context) => create(
context,
Provider.of<T>(context),
),
selector: (context) =>
(selector?.call(context, Provider.of<T>(context)) ?? [])
..add(Provider.of<T>(context)),
);
}
class SelectiveProvider2<T, T2, R> extends SelectiveProvider<T, R> {
SelectiveProvider2({
super.key,
super.child,
super.builder,
required SelectiveProviderBuilder2<T, T2, R> create,
super.dispose,
SelectiveProviderBuilder2<T, T2, List<dynamic>>? selector,
}) : super(
create: (context, value) => create(
context,
value,
Provider.of<T2>(context),
),
selector: (context, value) =>
(selector?.call(context, value, Provider.of<T2>(context)) ?? [])
..add(Provider.of<T2>(context)),
);
}
class SelectiveProvider3<T, T2, T3, R> extends SelectiveProvider2<T, T2, R> {
SelectiveProvider3({
super.key,
super.child,
super.builder,
required SelectiveProviderBuilder3<T, T2, T3, R> create,
super.dispose,
SelectiveProviderBuilder3<T, T2, T3, List<dynamic>>? selector,
}) : super(
create: (context, value, value2) => create(
context,
value,
value2,
Provider.of<T3>(context),
),
selector: (context, value, value2) => (selector?.call(
context, value, value2, Provider.of<T3>(context)) ??
[])
..add(Provider.of<T3>(context)),
);
}
class SelectiveProvider4<T, T2, T3, T4, R>
extends SelectiveProvider3<T, T2, T3, R> {
SelectiveProvider4({
super.key,
super.child,
super.builder,
required SelectiveProviderBuilder4<T, T2, T3, T4, R> create,
super.dispose,
SelectiveProviderBuilder4<T, T2, T3, T4, List<dynamic>>? selector,
}) : super(
create: (context, value, value2, value3) => create(
context,
value,
value2,
value3,
Provider.of<T4>(context),
),
selector: (context, value, value2, value3) => (selector?.call(
context, value, value2, value3, Provider.of<T4>(context)) ??
[])
..add(Provider.of<T4>(context)),
);
}
class SelectiveProvider5<T, T2, T3, T4, T5, R>
extends SelectiveProvider4<T, T2, T3, T4, R> {
SelectiveProvider5({
super.key,
super.child,
super.builder,
required SelectiveProviderBuilder5<T, T2, T3, T4, T5, R> create,
super.dispose,
SelectiveProviderBuilder5<T, T2, T3, T4, T5, List<dynamic>>? selector,
}) : super(
create: (context, value, value2, value3, value4) => create(
context,
value,
value2,
value3,
value4,
Provider.of<T5>(context),
),
selector: (context, value, value2, value3, value4) => (selector?.call(
context,
value,
value2,
value3,
value4,
Provider.of<T5>(context)) ??
[])
..add(Provider.of<T5>(context)),
);
}
class SelectiveProvider6<T, T2, T3, T4, T5, T6, R>
extends SelectiveProvider5<T, T2, T3, T4, T5, R> {
SelectiveProvider6({
super.key,
super.child,
super.builder,
required SelectiveProviderBuilder6<T, T2, T3, T4, T5, T6, R> create,
super.dispose,
SelectiveProviderBuilder6<T, T2, T3, T4, T5, T6, List<dynamic>>? selector,
}) : super(
create: (context, value, value2, value3, value4, value5) => create(
context,
value,
value2,
value3,
value4,
value5,
Provider.of<T6>(context),
),
selector: (context, value, value2, value3, value4, value5) =>
(selector?.call(context, value, value2, value3, value4, value5,
Provider.of<T6>(context)) ??
[])
..add(Provider.of<T6>(context)),
);
} as well as one for SelectiveChangeNotifierProviderclass SelectiveChangeNotifierProvider0<R extends ChangeNotifier?>
extends SelectiveProvider0<R> {
const SelectiveChangeNotifierProvider0({
super.key,
super.child,
super.builder,
required super.create,
super.dispose,
super.selector,
});
@override
State<SelectiveProvider0<R>> createState() =>
_SelectiveChangeNotifierProvider0<R>();
}
class _SelectiveChangeNotifierProvider0<R extends ChangeNotifier?>
extends _SelectiveProvider0State<R> {
@override
Widget buildWithChild(BuildContext context, Widget? child) {
assert(
widget.builder != null || child != null,
'$runtimeType used outside of MultiProvider must specify a child',
);
return ChangeNotifierProvider.value(
value: value as R,
child: widget.builder != null
? Builder(
builder: (context) => widget.builder!(context, child),
)
: child!,
);
}
}
class SelectiveChangeNotifierProvider<T, R extends ChangeNotifier?>
extends SelectiveChangeNotifierProvider0<R> {
SelectiveChangeNotifierProvider({
super.key,
super.child,
super.builder,
required SelectiveProviderBuilder<T, R> create,
super.dispose,
SelectiveProviderBuilder<T, List<dynamic>>? selector,
}) : super(
create: (context) => create(
context,
Provider.of<T>(context),
),
selector: (context) =>
(selector?.call(context, Provider.of<T>(context)) ?? [])
..add(Provider.of<T>(context)),
);
}
class SelectiveChangeNotifierProvider2<T, T2, R extends ChangeNotifier?>
extends SelectiveChangeNotifierProvider<T, R> {
SelectiveChangeNotifierProvider2({
super.key,
super.child,
super.builder,
required SelectiveProviderBuilder2<T, T2, R> create,
super.dispose,
SelectiveProviderBuilder2<T, T2, List<dynamic>>? selector,
}) : super(
create: (context, value) => create(
context,
value,
Provider.of<T2>(context),
),
selector: (context, value) =>
(selector?.call(context, value, Provider.of<T2>(context)) ?? [])
..add(Provider.of<T2>(context)),
);
}
class SelectiveChangeNotifierProvider3<T, T2, T3, R extends ChangeNotifier?>
extends SelectiveChangeNotifierProvider2<T, T2, R> {
SelectiveChangeNotifierProvider3({
super.key,
super.child,
super.builder,
required SelectiveProviderBuilder3<T, T2, T3, R> create,
super.dispose,
SelectiveProviderBuilder3<T, T2, T3, List<dynamic>>? selector,
}) : super(
create: (context, value, value2) => create(
context,
value,
value2,
Provider.of<T3>(context),
),
selector: (context, value, value2) => (selector?.call(
context, value, value2, Provider.of<T3>(context)) ??
[])
..add(Provider.of<T3>(context)),
);
}
class SelectiveChangeNotifierProvider4<T, T2, T3, T4, R extends ChangeNotifier?>
extends SelectiveChangeNotifierProvider3<T, T2, T3, R> {
SelectiveChangeNotifierProvider4({
super.key,
super.child,
super.builder,
required SelectiveProviderBuilder4<T, T2, T3, T4, R> create,
super.dispose,
SelectiveProviderBuilder4<T, T2, T3, T4, List<dynamic>>? selector,
}) : super(
create: (context, value, value2, value3) => create(
context,
value,
value2,
value3,
Provider.of<T4>(context),
),
selector: (context, value, value2, value3) => (selector?.call(
context, value, value2, value3, Provider.of<T4>(context)) ??
[])
..add(Provider.of<T4>(context)),
);
}
class SelectiveChangeNotifierProvider5<T, T2, T3, T4, T5,
R extends ChangeNotifier?>
extends SelectiveChangeNotifierProvider4<T, T2, T3, T4, R> {
SelectiveChangeNotifierProvider5({
super.key,
super.child,
super.builder,
required SelectiveProviderBuilder5<T, T2, T3, T4, T5, R> create,
super.dispose,
SelectiveProviderBuilder5<T, T2, T3, T4, T5, List<dynamic>>? selector,
}) : super(
create: (context, value, value2, value3, value4) => create(
context,
value,
value2,
value3,
value4,
Provider.of<T5>(context),
),
selector: (context, value, value2, value3, value4) => (selector?.call(
context,
value,
value2,
value3,
value4,
Provider.of<T5>(context)) ??
[])
..add(Provider.of<T5>(context)),
);
}
class SelectiveChangeNotifierProvider6<T, T2, T3, T4, T5, T6,
R extends ChangeNotifier?>
extends SelectiveChangeNotifierProvider5<T, T2, T3, T4, T5, R> {
SelectiveChangeNotifierProvider6({
super.key,
super.child,
super.builder,
required SelectiveProviderBuilder6<T, T2, T3, T4, T5, T6, R> create,
super.dispose,
SelectiveProviderBuilder6<T, T2, T3, T4, T5, T6, List<dynamic>>? selector,
}) : super(
create: (context, value, value2, value3, value4, value5) => create(
context,
value,
value2,
value3,
value4,
value5,
Provider.of<T6>(context),
),
selector: (context, value, value2, value3, value4, value5) =>
(selector?.call(context, value, value2, value3, value4, value5,
Provider.of<T6>(context)) ??
[])
..add(Provider.of<T6>(context)),
);
} these are very verbose to write, however, easy to use: class RemoteDataProvider extends SelectiveChangeNotifierProvider2<Settings, Client, RemoteDataService> {
RemoteDataProvider({String? url})
: super(
create: (context, settings, client) => RemoteDataService(
url: url,
settings: settings,
client: client,
),
);
} and can be adapted for sub-properties: class DatabaseProvider extends SelectiveChangeNotifierProvider<Settings, DatabaseService> {
DatabaseProvider()
: super(
create: (context, settings) => DatabaseService(
path: settings.dabatasePath,
),
selector: (context, settings) => [settings.dabatasePath],
);
} There might be a way to write this a bit shorter and conciser. I also first thought I might be able to build this by extending Maybe a similar structure to the above could be built inside of the library with a Do you think this would be worthwile, or this usecase just very specific to me? |
I have given this some though and I believe the most elegant way of integrating this would be to add a "selector" parameter to If that list by default would be empty, this could be done with full backwards compatibility, no breaking changes. This would also enable "stateless" (without a statefulwidget) recreation of Providers that depend on a Parameter that is passed in, for example an ID or something and for which it would be easier to recreate them than to reuse them like the update function in |
i think this might be too specific to my usecase to be part of the library. |
Is your feature request related to a problem? Please describe.
Some of the objects I want to provide can hold state and also depend on other providers above them.
Provider
offers two ways of handling this;Either I use the
Provider.update
function to recreate my object with the new state of the other providers,or I use a
ChangeNotifier
which then contains a method to update the object with the new parent states.Since my objects hold state themselves, recreating them in the update function of a
ProxyProvider
without any conditions cannot work, because it leads to over-recreation. Any rebuild will recreate them, which is undesirable and I lose state.This means I would have to fill the update function with If statements that only recreate said object if the parents changed, however, this would require state, so I have to make my widget stateful and store the last version of said parents, or something similar to that.
this does not seem very nice to me.
If my objects are
ChangeNotifiers
then I can use aChangeNotifierProxyProvider
, butChangeNotifierProxyProvider
is setup in a way that does not allow passing the dependencies into the object via the constructor.If my object is a
ChangeNotifier
, I now have to create a new method to sideload my parent state,which means my object can temporarily be in an "invalid" state, where the parent dependencies are not available yet.
This does not work well with objects that could be used without a
Provider
, where the parent dependencies are required.If I do not want to change my objects, then I have to wrap them with a
ChangeNotifier
that then recreates them internally.if they are themselves
ChangeNotifiers
then I now have a weird construct that wraps aChangeNotifier
with aChangeNotifier
so that I can replace the innerChangeNotifier
. it also means that I now have to unpack my innerChangeNotifier
everytime I grab it from context. This also does not seem appealing to me.Describe the solution you'd like
It would be nice if there was some easy way of having my providers only recreate their value when their dependencies change, kind of like a
select
but for provider values.I am not sure if I just missed something or if the thing that I am attempting would be bad practice or abuse of the way
Provider
works.Regardless, it is actually possible to kind of add this functionality in a reusable way from the outside, with a
mixin
as I have done here:https://gist.github.com/clragon/cadf818800c86b60b9c560673ecf6356
this adds a
guard
function matching eachProxyProvider
/ChangeNotifierProxyProvider
variant and works similar to aselect
by checking a list of values for deep equality. It is used like this:there are some problems with this, for example that it requires a mixin to hold the state for comparison and because the creation method is now held inside the guard function, I need to also pass the dispose function twice.
If this functionality was provided inbuilt in some way, it would probably be easier and nicer.
I am not sure how exactly this would take shape in the library, however.
edit: I have just thought about it, and this might be possible if I were to implement my own
Provider
based on one of the for this made classes. I haven't really looked at them before, so I don't know however.Describe alternatives you've considered
Many of the alternatives have been listed above, but are undesirable in some way.
Additional context
I have setup a small project to play around with this conundrum.
In there, I am manually writing out all the code necessary to make this work without the guard mixin.
Code
The text was updated successfully, but these errors were encountered: