Closed lukepighetti closed 2 months ago
Hey Luke, thanks for the feedback. I understand your frustration.
My intention for context_watch
was to replace the *Builder
widgets for reactive data with something that doesn't introduce nesting, but provides all the same data these builders provide, in a unified fashion.
The very first implementation, even though slightly easier to write, was not very useful for async primitives:
Stream
s (and for ValueStream
s passed as Stream
s for that matter).Future
s.ValueStream
s, it was actually neglecting the fact that the value is allowed to be uninitialized.Returning AsyncSnapshot
from the .watch
call resolves all that and provides the same values, which respective *Builder
widgets provide:
ValueListenable.watch()
≈ ValueListenableBuilder
Listenable.watch()
≈ ListenableBuilder
Future.watch()
≈ FutureBuilder
Stream.watch()
≈ StreamBuilder
The beauty of it is that it allows to have a very easy-to-remember API for every possible observable type:
Anything.watch()
subscribes you to the current state of the observable valueAnything.watchOnly()
subscribes you to the changes in a selected subset of the observable value's current stateOnly two extra methods for every observable type, returning everything you might want to know about the state of the observable.
All potential alternatives I know of (including your proposal) blow that number up, effectively increasing the complexity ("which method should I use in these circumstances?") and making the learning curve steeper, which I want to avoid.
Speaking of the migration path, these should be equivalent:
context_watch v1 | context_watch v3 |
---|---|
final data = ValueStream.watch(context); |
final data = ValueStream.watch(context).requireData; |
ended up with this extension which I now cart around to all my projects. the purpose is to make it easy to get the underlying data without error handling (as it was previously, I know very few people who actually make use of the whole AsyncSnapshot API). null means nothing available yet. perhaps this could be useful to you
extension ValueStreamX<T> on ValueStream<T> {
T watchValue(BuildContext context) => watch(context).data ?? value;
}
@lukepighetti, looks like the watchValue
extension you use can be simplified to the watch(context).data!
or watch(context).requireData
since context_watch handles a special case of ValueStream
's initial value being already present under the hood and returns the data in that case in the same frame.
Regarding the package API design decision, I don't want to get rid of the AsyncSnapshot
as it allows for keeping the API surface really tiny and easy to remember and use. I love the fact that there are only two additional methods on everything observable: .watch()
(for observing the whole thing) and .watchOnly()
(for observing a part of the thing) and don't plan on adding any new methods to the base packages.
However, I think it would be fairly easy to introduce another "integration" package for rxdart specifically, with the .watchValue()
and .watchValueOrNull()
extensions, which would just always return the respective property values of the ValueStream
. Please let me know if that could be of any value to you.
I totally get the reasoning behind changing the API to use AsyncSnapshot but part of the beauty of context_watch was the simplicity of the API, would have loved to see
.watch
stay as it was, and then add a.watchSnapshot
with the AsyncSnapshot return type