-
Notifications
You must be signed in to change notification settings - Fork 27.2k
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
Need mechanism for determining current build target #128461
Comments
It affects performance because it's an assignment before every build and an assignment after every build. That's thousands of assignments per frame, to a memory location that is using up cache lines, in a way that might prevent inlining, etc. These code paths are super hot and we really want to do as little work as possible. Your use case is interesting. Generally I would recommend passing the |
That's a fair suggestion; however, I really want to avoid having to change ReactiveWidget(() => Text('${counter.value}')) into ReactiveWidget((context) => Text('${counter.getValue(context)}')) because I'm trying to avoid as much boilerplate syntax as possible. I guess the current solution I have, of recording the current I have been told that |
I understand where you're coming from, but the problem is that you're experiencing the tension between making an API look clean and easy to write, and making an API easy to understand. Global variables, getters with side-effects, and the combination of those, code that operates on data it doesn't seem to have (in your case, the caller's identity) are all forms of "magic" that make for very nice-looking code that is hard to understand. When you see On the other hand, when you see Flutter leans strongly into this second paradigm (the code should look like what it does).
In normal use, that is correct. The |
In general I hate magic / implicit behavior. It's why I can't stand C++, among other things. So we're on the same page. However I have an even harder time with unnecessary complexity -- and all the other state management solutions out there for Flutter are horrendously complex. This is why I sacrificed on the first of these two problems to try to solve the second one. My thinking was to treat this as a dataflow problem, where the dependency graph is created by merely reading a value. So the philosophy here is to make Dart look like a reactive dataflow language. Dart is not a dataflow language though, and you made some very convincing arguments, especially with the suggested method name change to So I'll make the breaking changes you suggested. However, maybe it would be good to figure out a better (much simpler!) state management solution as a provided default for Flutter? |
@Hixie OK, I started to make the changes, but I don't understand how to find the nearest ancestor
and
I'm lost here... (1) How are you supposed to find ancestor widgets from a (2) I'm not trying to find the ancestor from a (3) I need the state object of the What I have is this -- but based on the above, I don't think it's correct: class ReactiveValue<T> extends ChangeNotifier {
ReactiveValue(this._value);
T _value;
T getAndSubscribe(BuildContext context) {
final parentReactiveWidgetState = context.findAncestorStateOfType<_ReactiveWidgetState>();
assert(parentReactiveWidgetState != null, 'Could not find parent ReactiveWidget');
parentReactiveWidgetState?._listenTo(this);
return _value;
}
void set(T newValue) {
if (_value != newValue) {
_value = newValue;
notifyListeners();
}
}
} |
Can you elaborate on why you need a I was imagining that the subscription mechanism would just remember the |
@Hixie Oh, I didn't even know (So to answer your question, the reason for I'll try out the solution you suggested, that's awesome. Thanks. |
@Hixie This is what I have, but there are some issues... class NewReactiveValue<T> {
NewReactiveValue(this._value);
T _value;
final _subscribedElements = <Element>{};
T getAndSubscribe(BuildContext context) {
_subscribedElements.add(context as Element);
return _value;
}
void set(T newValue) {
if (_value != newValue) {
_value = newValue;
notifyListeners();
}
}
void notifyListeners() {
for (final element in _subscribedElements) {
if (element.mounted) {
element.markNeedsBuild();
}
}
}
} How do I track the lifecycle of the element, so that I can remove the listener when it's no longer needed? Is there a way to subscribe to the Sorry for all the basic questions, I find it very hard to figure this stuff out from the documentation... |
There's no way to be notified when an element goes away. This would be a good use case for An element is guaranteed to rebuild if it is ever mounted after being unmounted. (In practice you won't observe an element in its unmounted state because if it's not immediately remounted it'll be disposed. Elements don't survive past the end of the frame if they're not mounted.)
No worries! If you could enumerate all the questions you've asked and where you looked for answers for each one, I can make sure to go and add answers to those places. |
@Hixie Sorry, but honestly I have read so much documentation both on the Flutter site and on other sites that I don't even know what to suggest improving. The UI system is very complex in Flutter, and it's hard to get one's mind around all of it. Not many Flutter developers will try to do anything as low-level or out-of-the-ordinary as what I took on for this project, so I don't think my learning curve issues will be representative of what others need anyway. Thanks for all the pointers. Now what I have is the following: class ReactiveValue<T> {
ReactiveValue(this._value);
T _value;
final _subscribedElements = <WeakReference<Element>>{};
/// Get the current value, and cause the [Element] passed as this
/// [BuildContext] to subscribe to future changes in the value.
T getAndSubscribe(BuildContext context) {
_subscribedElements.add(WeakReference(context as Element));
return _value;
}
/// Set the value, and notify subscribers if value changed.
void set(T newValue) {
// Don't allow mutating state during `build`
// https://github.com/flutter/flutter/issues/128384#issuecomment-1580110349
assert(
SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks,
'Do not mutate state (by setting a ReactiveValue\'s value) '
'during a `build` method. If you need to schedule an update, '
'use `SchedulerBinding.instance.scheduleTask` or similar.');
if (_value != newValue) {
_value = newValue;
notifyListeners();
}
}
/// Update the value based on current value, and notify subscribers
/// if value changed.
/// e.g. to increment: reactiveVal.update((v) => v + 1)
void update(T Function(T) currValueToNextValue) {
set(currValueToNextValue(_value));
}
/// You should manually invoke this method for deeper changes,
/// e.g. when items are added to or removed from a set, as in
/// ReactiveValue<Set<T>>
void notifyListeners() {
final liveSubscribedElements = <WeakReference<Element>>[];
_subscribedElements.forEach((elementWeakRef) {
// Skip elements that have already been garbage collected
final element = elementWeakRef.target;
if (element != null) {
// For any subscribing elements that are still live, mark element
// as needing to be rebuilt
element.markNeedsBuild();
// Remember elements that are still live
liveSubscribedElements.add(elementWeakRef);
}
});
// Remove any elements that have been garbage collected
_subscribedElements.clear();
_subscribedElements.addAll(liveSubscribedElements);
}
@override
String toString() => '${describeIdentity(this)}($_value)';
} This finally seems to fully work as expected, and it's a much better solution than what I had before. Can you please check over the code and let me know if there are any outstanding issues? Dart needs a single ultra-simple reactive state management solution as a default, to lower the barrier of entry to newcomers (since state management seems to be the hardest part to learn when learning Flutter). What is the likelihood that the above class, or something like it, could be incorporated into the Flutter core libraries? |
Actually I just thought of an issue with this -- the set I assume then that I have to extend class _HashableWeakReference<T extends Object> {
late WeakReference<T> _weakReference;
_HashableWeakReference(T value) {
_weakReference = WeakReference(value);
}
T? get target => _weakReference.target;
@override
bool operator ==(Object other) =>
other is _HashableWeakReference<T> &&
identical(_weakReference.target, other._weakReference.target);
@override
int get hashCode => _weakReference.target.hashCode;
} |
Fair enough. In the future please, any time something is unclear and you don't find the answer right away in our docs, file a bug saying what your question was and where you looked.
That doesn't matter, we want to make it easy for everyone, not just people doing the basic stuff.
Seems reasonable to me, modulo the issue you pointed out (that I would not have noticed, to be honest). Your solution to the WeakReference stuff seems reasonable.
I think it would make a lot of sense for such a package, once suitably proven, to be listed on https://docs.flutter.dev/data-and-backend/state-mgmt/options. (Currently our recommended solution is Personally I see state management as the core of writing an application. I don't think it makes that much sense to create packages for it, I just build a bespoke solution for each application based on the shape of its data model, the access patterns, etc. I wouldn't recommend using |
Thanks, I really appreciate the commitment to producing good documentation. I filed the first documentation improvement request: #128648
Thanks for taking a look. It occurred to me I could make this even simpler: class _ListenerWrapper<T> {
void Function()? listener;
}
extension SubscribableValueNotifier<T> on ValueNotifier<T> {
T getValueAndSubscribe(BuildContext context) {
final elementRef = WeakReference(context as Element);
// Can't refer to listener while it is being declared, so need to add
// a layer of indirection.
final listenerWrapper = _ListenerWrapper<void Function()>();
listenerWrapper.listener = () {
assert(
SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks,
'Do not mutate state (by setting the value of the ValueNotifier '
'that you are subscribed to) during a `build` method. If you need '
'to schedule an update after `build` has completed, use '
'`SchedulerBinding.instance.scheduleTask(updateTask, Priority.idle)` '
'or similar.');
// If the element has not been garbage collected, mark the element
// as needing to be rebuilt
elementRef.target?.markNeedsBuild();
// Remove the listener -- only listen to one change per `build`
removeListener(listenerWrapper.listener!);
};
addListener(listenerWrapper.listener!);
return value;
}
}
You are right, it's not a be-all and end-all for state management. It's intended for getting users onboarded to Flutter quickly and easily, for apps with minimal reactivity requirements. I do think there's a solid need for that in the Flutter core libraries. |
@Hixie I released this library as: https://github.com/lukehutch/flutter_reactive_value Thanks for all the significant feedback you provided to make this happen. I am very happy with the final result, and I couldn't have done it without your guidance! |
Congratulations! |
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of |
Is there an existing issue for this?
Use case
(Moved over from #128384 -- cc @Hixie)
I need a way to determine which widget is currently being built.
WidgetsBinding.instance.buildOwner._debugCurrentBuildTarget
is not exposed (and is not even available in release builds). (@Hixie noted that it would be expensive to track this in release builds, although the current code that tracks this is just one assignment on the way into abuild
method, and one assignment on the way back out, so I don't see how that would be expensive...)The reason I need this information is because I created this library, in which a user subclasses
ReactiveWidget
(which subclassesStatefulWidget
), and then whenever their widget is getting built, any read of thevalue
of aReactiveValue
(which subclassesValueNotifier
) causes a listener to be added to theReactiveValue
that causessetState
to be called on theReactiveWidget
whenever the value changes.I know that
build
methods should not mutate state -- more strictly, that they should not have side effects. This does not mutate state (at least per se), it just adds a listener. Yes, I know some people will hate this approach for this reason alone. However this is by far the simplest reactive UI update mechanism available so far for flutter.The big problem I have is that in order to figure out which
ReactiveWidget
to call from theReactiveValue
listener, I need to know which widget is being built when theReactiveValue
'svalue
field is read while the widget'sbuild
method is being run.The solution that I have, which is horrible, but works (currently, at least!), is to record the current
ReactiveWidget
in a global variable when entering thebuild
method, then unrecord it on exit frombuild
. (I put these into a stack, although I think there can only ever be zero or onebuild
method(s) running.)https://github.com/lukehutch/flutter_reactive_widget/blob/main/lib/src/reactive_widget.dart#L43
Proposal
It would be much better if I could just read from
WidgetsBinding.instance.buildOwner.currentBuildTarget
.I would like to see some benchmarks to see if always tracking the build target really does impact performance. I highly doubt the impact of setting this field on entry to
build
and unsetting it on exit would even be measurable.The text was updated successfully, but these errors were encountered: