Open passsy opened 2 months ago
I've almost finished the site with various usage examples. Hopefully, I'll publish it towards the end of the next week. Will let you know.
Regarding the migration, there won't be 1:1 correspondence, but I believe context_plus supports the same amount of usecases as Provider, or maybe even more.
You can:
.bind()
(~Provider(lazy: false)
.bindLazy()
(~Provider(lazy: true)
.bindValue()
(~ValueProvider
).bind(key: )
, .bindLazy(key: )
(~ProxyProvider
). Example:
final value1 = value1Listenable.watch(context);
final value2 = value2Listenable.watch(context);
final value3 = _value3Ref.bind(
context,
() => Value3(value1, value2),
key: (value1, value2),
);
Now, whenever value1 or value2 changes, the Value3 will get recreated. Works more-or-less like keys on widgets.
AsyncListenable
and provide it either via Ref<AsyncListenable>.bind()
or as a part of some other object.Also, Ref<AnimationController>
and a couple of other types of refs provide an additional vsync
parameter so that you can create and bind them easily as well.
The whole API is intentionally kept minimal-ish:
Ref.bind() - create eagerly, then bind
Ref.bindLazy() - bind, then create lazily upon first request
Ref.bindValue() - just bind
(Stream/Future/Listenable/ValueListenable/AsyncListenable/Ref<Stream>/Ref<Future>/Ref<Listenable>/Ref<ValueListenable>/Ref<AsyncListenable>/...).watch() - rebuild on any notification
(Stream/Future/Listenable/ValueListenable/AsyncListenable/Ref<Stream>/Ref<Future>/Ref<Listenable>/Ref<ValueListenable>/Ref<AsyncListenable>/...).watchOnly() - rebuild only when selected value changes upon notification
Hope that helps. Stay tuned for the site, I love how it pans out so far.
Hi, @s0nerik @jinyus @passsy,
I think introducing a bindProxy
method, similar to the ProxyProvider would be more convincing and eliminate the need for users to manually manage keys.
final value1 = value1Listenable.watch(context);
final value2 = value2Listenable.watch(context);
final value3 = _value3Ref.bind(
context,
() => Value3(value1, value2),
key: (value1, value2),
);
// case 1
final value = _value3Ref.bindProxy(
context,
(context, T? previous) {
final value1 = value1Listenable.watch(context);
final value2 = value2Listenable.watch(context);
return Value3(value1, value2);
},
);
// case 2
// or we can use the previous value without the need to create a new one
final value = _value3Ref.bindProxy(
context,
(context, T? previous) {
final value1 = value1Listenable.watch(context);
final value2 = value2Listenable.watch(context);
if (previous != null) {
previous.valueOne = value1;
previous.valueTwo = value1 + value2;
return previous;
}
return Value3(value1, value2);
},
);
In this new approach, keys are managed internally, simplifying the API for users.
For Case 2, where we want to reuse the previous object if possible to avoid disposing of it, I see two potential solutions:
Add a property autoDisposePrevious to bindProxy, allowing users to control whether the previous instance should be automatically disposed of or retained.
Compare the hashcode of the returned object with the previous object. If they match, there's no need to dispose of the previous object. However, care must be taken when dealing with records, as comparing them by reference can be tricky.
@yousefak007 The examples you provide in support of the .bindProxy()
are, IMO, easier to represent without it:
// case 1 (When `Value3` is a data object)
final value1 = value1Listenable.watch(context);
final value2 = value2Listenable.watch(context);
final value = _value3Ref.bindValue(context, Value3(value1, value2));
// case 1 (When `Value3` is a controller)
final value1 = value1Listenable.watch(context);
final value2 = value2Listenable.watch(context);
final value = _value3Ref.bind(
context,
() => Value3(value1, value2),
key: (value1, value2),
);
// case 2
final value1 = value1Listenable.watch(context);
final value2 = value2Listenable.watch(context);
final value = _value3Ref.bind(context, () => Value3(value1, value2))
..valueOne = value1
..valueTwo = value1 + value2;
2/3 cases can avoid using keys, and the one that requires a key seems like a better approach to me than trying to force all controller types to have a meaningful equals
/hashCode
pair.
UPD. On the other hand, though, tinkering with a BuildContext
provided to .bindProxy()
and comparing the returned values by the identity
might just work... I'll think about it for some time, will be glad to hear what you guys think.
@s0nerik I came up with this solution, take a look: and I think it's more composable, and easy to do, rather than remembering to put keys the keys hell make more bugs if you forget to put it like Reactjs
// we need to define it in somewhere shared for all libraries
final _keys = [];
// in context_watch:
// inside watch method
T watch(BuildContext context) {
InheritedContextWatch.of(context)
.getOrCreateObservable(context, this)
?.watch();
// just add the value to build keys
_keys.add(value);
return value;
}
// in context_ref:
// inside bind method
ValueProvider<T> bind<T>({
required BuildContext context,
required Ref<T> ref,
required T Function() create,
required void Function(T value)? dispose,
required Object? key,
}) {
assert(context is Element);
....
final provider = ref.getOrCreateProvider(context);
// we need to make disposer before the key
provider.disposer = dispose;
provider.key = key;
....
}
// Impl. of Proxy method
T bindProxy(
BuildContext context,
T Function(BuildContext context, T? previous) create, {
void Function(T value)? dispose,
}) {
final previousValue = this._providers[context]?.value;
final value = create(context, previousValue);
void Function(T value)? disposeFn = dispose;
// in somehow figure how is previous used or not
// solution 1:
// add {bool autDisposePrevious = true} to parameters
// then use condition to dispose or not
if (autDisposePrevious == false) {
// this will prevent the dispose from working
disposeFn = (_) {};
}
// solution 2:
// compare between the hashcode of previous value and the current value
// then dispose if there is difference between references
// but should find away if the value is record
if (value.hashCode == previousValue.hashCode) {
// this will prevent the dispose from working
disposeFn = (_) {};
}
final result = bind(
context,
() => value,
dispose: disposeFn,
key: _keys,
);
_keys.clear();
return result;
}
@s0nerik I think the proxy method will have 3 main features:
1- have direct access to the previous value 2- have the ability to update the previous value without the need to create a new instance (so element tasks like initializations or maybe make some HTTP requests once every change) 3- a better way to manage keys rather than depending on adding them manually
in my opinion, this library will be replacing Provider and it solves the Provider missing features, so we need this library to cover the features that Providers can do
@yousefak007 I've created a separate issue to track the potential Ref.bindProxy()
implementation. Feel free to comment there: https://github.com/s0nerik/context_plus/issues/16
I'd like to have a quick small guide how to migrate from
provider
tocontext_plus
.Basic usage works, but I'd like to know if I can migrate more advanced topics like
ProxyProvider
or sub scopes.