ref.update No-op
Closed this issue · 7 comments
If I am understanding this correctly, the current logic behind ref.update seems to be calling ref.set internally and checking if the state has changed before notifying the relevant Watchers
/// Set state of the creator. Typically this is used to set the state for
/// creator with no dependency, but the framework allows setting state for any
/// creator. No-op if the state doesn't change.
void set<T>(CreatorBase<T> creator, T state) {
final element = _element<T>(creator, recreate: false);
final before = element.state;
if (before != state) {
element.prevState = element.state;
element.state = state;
element.error = null;
_onStateChange(creator, before, state);
}
}
/// Set state of creator using an update function. See [set].
void update<T>(CreatorBase<T> creator, T Function(T) update) {
set<T>(creator, update(_element(creator).state));
}
I believe there are going to be some potential issues with this check below when the type of the creator is more complex?
if (before != state)
Such as a class which has it's own fields and values, ex:
https://dartpad.dev/?id=9cb6c58e667da441c6d3256964c247dd
I would expect the code above to work when calling update, but it doesn't.
Even in simpler cases, I would think the current documentation implies, the only No-op should be ref.set whereas ref.update should notify watchers even if the state is the same?
Hi @borjandev borjandev, you are right that both set
and update
doesn't look into the internal of object T
. The idea is that we should use immutable data, which will have fewer subtle bugs. Sorry, I should have been clear about this and will add documentation.
So your Counter
should be immutable and maybe have a Counter increment()
API which returns a new counter.
I guess you probably have a more complex use case in mind. In practice, I typically do two things:
- Define multiple creators rather than put them inside a data class.
For example, I will do:
// user_logic.dart
final loading = Creator.value(false);
final user = Emitter<User>((ref, emit) async {
ref.set(loading, true);
emit(await fetchUser());
ref.set(loading, false);
});
Rather than
// user_logic.dart
class UserState {
final User? user;
final bool loading;
// Need to implement == and hash function, or use equatable package, or freezed package.
}
final user = Emitter<UserState>((ref, emit) async {
emit(UserState(null, true));
final user = await fetchUser();
emit(UserState(user, false));
});
- If I really need data class, I will use
freezed
orequatable
+ DIY copy function to make them immutable.
I hope this makes sense. Happy to discuss.
Hi @terryl1900 thank you for the prompt response, aiming to work with immutable classes makes sense, however even without classes getting involved, I would expect :
ref.set // No-op if the state doesn't change
ref.update // Notifies my watcher every time even if the state is the same
https://dartpad.dev/?id=ff7b992c695e0a6e21f9add195f2062b
In the dartpad above, I expect pressing the '5' button to notify my watcher every time the button is pressed, and not just the first time that the state changes to '5', as I am using ref.update
so I am always expecting an operation to occur
If I didn't want the watcher to update on the same data I wold then just use ref.set
Are these expectations aligned with the functionality that Creator aims to provide out of the box?
Hmm as of now, it is designed that creator only propagate state changes, and update
works the same as set
in this regards.
Update exists just because it is convinent to implement things like "plus one". User doesn't need to read then set the creators.
If you have a use case that need to notify others, why not just model it as something (e.g. a class) which could have a different state every time you update or set?
Yes, the proposed solution of modeling the creator as a class and passing new instances of that class with different state every time was something that I attempted internally, and it did work as expected, I was just unsure if it's the recommended way to do it.
I suppose the documentation should indicate in much more detail that if the type T of the Creator is a type that can hold additional state by itself, ex: T is a class with multiple fields, that the proper way to update, for example, a single field of that class while maintaining the state of the other fields, would be by creating a "copyWith" method or a similar on the original class and using that to create a brand new instance of the class when setting or updating the Creator.
The main goal / purpose of using a class would be for example if you have a simple app that draws a shape provided the sides and the angle, and if you wanted to draw the shape in a single frame, so that when you do something like "pump" when writing tests, the whole shape will be drawn on the next frame when updating the creator ex:
class Shape {
final int sides = 3
final double angle = 60.0
}
Should draw a triangle in a single frame.
class Shape {
final int sides = 4
final double angle = 90.0
}
Should draw a rectangle in a single frame.
If those were two separate creators such as
final sides = Creator.value(3);
final angle = Creator.value(90.0);
And if I have a Watcher monitoring for both of those creators, whenever I update the value of the sides Creator, then the angle Creator, there would be a very brief moment, perhaps a single frame, where the result would be incorrect as the Watcher would react on the first change of sides, then the change of angle?
Unless, @terryl1900 is some better way of updating both creators simultaneously in a single frame maybe by using an async function or similar?
perhaps a single frame, where the result would be incorrect as the Watcher would react on the first change of sides, then the change of angle
Watcher would react on the first change of sides, then the change of angle
is true, perhaps a single frame
is not. If you set one value and then another, Watch's creation functions got triggered twice. However, it should all happen before the next Flutter build frame, so UI still gets the latest data from the second creation.
I see your point. When I said Define multiple creators rather than put them inside a data class
, I actually mean the view-model classes, where the values are not tied so close logically. If the class is more like a data model, e.g. Shape
or User
, it definitely makes sense to group them together.
I suppose the documentation should indicate in much more detail
Yeah, will add documentation today.
Thanks for the info @terryl1900!
That makes sense, it may not show visually in the next frame, however the widget which is being returned by that Watcher will rebuild twice "under the hood" so to speak (if I update the sides, then the angle immediately one after the other when using multiple creators)
once with :
sides: 4
angle: 60
and then with :
sides: 4
angle: 90
Which in some cases may be undesirable,
With all of the info you provided and the updated documentation things are much clearer now, thanks again for the prompt responses!
Thanks for the contribution!