either<R> method
R
either<R>(
- R onValue(
- T value
- R onNoValue(
- 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),
};
}