<ValueListener>
Overview
The <ValueListener> component creates a stateful widget that listens for changes to a
named dependency value and rebuilds its children when that value changes. It is the primary
mechanism for reactive UI updates in XWidget. Notifications are path-based: a listener rebuilds
when a write path passes through the notifier created for the listener's dependency path.
Under the hood, <ValueListener> wraps the watched value in a ValueNotifier using
Dependencies.listenForChanges(). When the value changes, Flutter's ValueListenableBuilder
triggers a rebuild of the listener's children.
Attributes
| Name | Type | Description | Required | Default |
|---|---|---|---|---|
varName |
String | The dependency key to watch for changes | Yes | - |
initialValue |
dynamic | Initial value to set if the dependency doesn't exist yet | No | null |
defaultValue |
dynamic | Default value to use if the dependency resolves to null | No | null |
varDisposal |
VariableDisposal | Controls how the notifier is disposed when the widget is removed. Values: none, byOwner, byLastListener |
No | VariableDisposal.none |
key |
Key | Widget key for controlling widget identity | No | null |
for |
String | The name of the parent's attribute that will be assigned this component | No | null |
visible |
bool | Controls widget visibility | No | true |
Variable Disposal
The varDisposal attribute controls what happens to the underlying ValueNotifier when
the <ValueListener> widget is disposed:
| Value | Behavior |
|---|---|
none |
The notifier is not disposed. The dependency value persists. This is the default. |
byOwner |
The notifier is disposed only if this listener is the one that created it. |
byLastListener |
The notifier is disposed only if no other listeners remain attached to it. |
When a notifier is disposed, the dependency value is also removed from the Dependencies
instance.
Examples
Basic Value Listening
<Controller name="CounterController">
<ValueListener varName="count">
<Text data="Count: ${toString(count)}"/>
</ValueListener>
</Controller>
class CounterController extends Controller {
var count = 0;
@override
void bindDependencies() {
dependencies.setValue("count", count);
dependencies.setValue("increment", increment);
}
void increment() {
dependencies.setValue("count", ++count);
}
}
When increment() is called, the count value is updated via setValue, which
triggers the <ValueListener> to rebuild and display the new count.
Listening to Nested Model Properties
<ValueListener varName="profile">
<Column>
<Text data="${profile.first}"/>
<Text data="${profile.last}"/>
<Text data="${profile.email}"/>
</Column>
</ValueListener>
final user = Model({
"first": "Mike",
"last": "Smith",
"email": "[email protected]",
});
dependencies.setValue("profile", user);
Changes to nested properties will trigger this listener when the write goes through the listened path:
A direct write that starts from the model object itself changes the model, but it does not
pass through the profile notifier:
For a deeper explanation of path-based notification, see State And Reactivity.
With Initial and Default Values
<ValueListener varName="selectedTab" initialValue="${0}" defaultValue="${0}">
<Text data="Selected tab: ${toString(selectedTab)}"/>
</ValueListener>
initialValue sets the value if the dependency doesn't exist yet. defaultValue
is used if the dependency resolves to null.
With Variable Disposal
<ValueListener varName="tempData" varDisposal="byOwner">
<Text data="${tempData}"/>
</ValueListener>
When this listener is removed from the widget tree, the tempData dependency is
also cleaned up since this listener created the notifier.
Multiple Listeners on the Same Value
Multiple <ValueListener> widgets can watch the same dependency. All of them rebuild
when the value changes:
<Column>
<ValueListener varName="count">
<Text data="Header count: ${toString(count)}"/>
</ValueListener>
<!-- other widgets -->
<ValueListener varName="count">
<Text data="Footer count: ${toString(count)}"/>
</ValueListener>
</Column>
See Also
<EventListener>- For responding to application-wide events<Controller>- For managing state and exposing data via dependencies<DynamicBuilder>- For async data loading