either<R> method

R either<R>(
  1. R onValue(
    1. T value
    ),
  2. R onNoValue(
    1. E? failure
    )
)

Collapse the full lifecycle into just two branches:

  • "we have a usable value" -> onValue
  • "we do not have a value" -> onNoValue

Usable-value states (onValue):

  • Ready(value)
  • Dirty(value, kind)
  • Updating(value)
  • Loading(prev != null)
  • Failure(prev != null) // last known good value

No-value states (onNoValue):

  • Failure(failure, prev == null) -> onNoValue(failure)
  • Uninitialized / Empty / Loading(no prev) -> onNoValue(null)

This is a structural view of your lifecycle: “do I currently have something I can treat as a value or not?”.

How you use it is up to the layer (repo/controller/UI). Recommended:

  • repos: still prefer explicit transforms or withDomainDefault(...)
  • controllers: good place to build UI flags (showSkeleton, hasError, etc.)
  • UI: should usually consume higher-level values/flags, not call value() directly.

Implementation

R either<R>(
    R Function(T value) onValue,
    R Function(E? failure) onNoValue,
    ) {
  final d = this;
  return switch (d) {
    Dirty<T, E>(value: final v, kind: _) ||
    Updating<T, E>(value: final v) ||
    Loading<T, E>(prev: final v?) ||
    Failure<T, E>(failure: final _, prev: final v?) ||
    Ready<T, E>(value: final v)  => onValue(v),

  // failure with no previous usable value
    Failure<T, E>(failure: final f, prev: null) =>
        onNoValue(f),

  // no usable value at all
    Uninitialized<T, E>() ||
    Empty<T, E>() ||
    Loading<T, E>(prev: null) =>
        onNoValue(null),
  };
}