set method

void set(
  1. T newValue
)

Sets the value of this node and notifies all active subscribers.

If newValue is equal to the current value (via ==), this is a no-op: the value is unchanged and no subscribers are notified. That includes AsyncData / AsyncLoading / AsyncError when their payloads compare equal (for example AsyncData(sameUser) twice).

Do not re-set an equal value to kick side effects (resume a deferred scope, re-run sync, refresh listeners). Use invalidate to force a notification without changing the value, or model the signal on a dedicated node / callback instead of piggybacking on identity state.

If the node has been disposed, the call is silently ignored (a concise debug message is printed in debug mode). This soft-fail behavior prevents crashes from late async writes that land after scope disposal.

Implementation

void set(T newValue) {
  if (_isDisposed) {
    // Late async writes after disposal are common and usually harmless (the
    // UI/scope is simply gone). Fail softly instead of throwing so callers
    // don't need to manually guard every post-await mutation.
    assert(() {
      Zenith.onDebugPrint?.call(
        'ZenithNode<$T>.set() ignored: node is disposed',
      );
      return true;
    }());
    return;
  }

  T effectiveValue = newValue;
  for (final mw in _middleware) {
    final intercepted = mw.onWillSet(this, _value, effectiveValue);
    if (intercepted == null) {
      return; // Write canceled by middleware
    }
    effectiveValue = intercepted;
  }

  if (_value == effectiveValue) {
    return;
  }

  final oldValue = _value;
  if (oldValue is Zeroizable && !identical(oldValue, effectiveValue)) {
    oldValue.zeroize();
  }
  _value = effectiveValue;

  Zenith.observer?.onNodeMutated(this, oldValue, effectiveValue);

  notifySubscribers();

  for (final mw in _middleware) {
    mw.onDidSet(this, effectiveValue);
  }
}