EffectRunner class abstract interface

An active standalone execution terminal that imperatively controls the lifecycle of an Effect.

While declarative Effect nodes are typically tethered to reactive tracking scopes via BloomVia.tether, an EffectRunner enables executing effects imperatively outside reactive scopes (e.g. in domain services, ViewModels, or background workers).

System Architectural Flow

┌────────────────────────────────────────────────────────┐
│                Declarative Effect Node                 │
└───────────────────────────┬────────────────────────────┘
                            │ (Active Subscription Edge)
                            ▼
┌────────────────────────────────────────────────────────┐
│                      EffectRunner                      │
├────────────────────────────────────────────────────────┤
│ • start()               ──> Attaches edge, resumes runs │
│ • stop()                ──> Relinquishes edge (Dormancy)│
│ • isActive             ──> Dual-state health check     │
└────────────────────────────────────────────────────────┘

Key Invariants & Architectural Benefits

  1. Zero-Leak Inactive Dormancy: When stopped via stop, immediately relinquishes its subscription edge, guaranteeing dormant upstream subgraphs when unobserved.
  2. Imperative Standalone Lifecycle: Controls reactive side-effect execution in services, view models, or CLI tasks outside widget lifecycles.
  3. Subscriber-Driven Dormancy Invariant: If the same Effect instance is shared between a tracking scope (via.tether) and an EffectRunner, stopping the runner leaves the effect active as long as the tethering scope remains active. The underlying effect enters true dormancy only when all observing terminals and tethers have detached.
  4. Dual-State Health Invariant: isActive reflects both the runner's lifecycle state and the underlying effect terminal's status. If the underlying effect deactivates due to exhausting its take ticket quota (Early Teardown), isActive transitions to false automatically.

Warning (Anti-Pattern Prevention):

  • Do NOT call .start() repeatedly inside reactive derivations or request handlers without stopping; unmanaged runners stay attached to upstream effects and cause memory leaks.

BAD:

// [Anti-Pattern] Spawning unmanaged effect runners inside a computed derivation
final activeQuery = computed((via) {
  searchEffect.start(); // Leaks a new runner on every evaluation!
  return via(query.bloom);
});

GOOD:

// [Recommended] Manage runner in a long-lived service lifecycle or tether via via.tether()
class SearchService {
  late final EffectRunner _runner;

  void init() {
    _runner = searchEffect.start();
  }

  void dispose() => _runner.stop();
}

Examples

final query = blooming('');
final searchEffect = Effect.debounce(
  [query.bloom],
  const Duration(milliseconds: 300),
  () => apiService.search(untracked(query.bloom)),
);

// Start standalone execution:
final runner = searchEffect.start();

// Pause observation:
runner.stop();

// Resume observation:
runner.start();
Implemented types
Available extensions

Constructors

EffectRunner(Effect effect)
Creates an inactive, dormant EffectRunner managing execution of effect.
factory

Properties

debugReflector → LpdagReflector?

Available on LpdagStatus, provided by the LpdagStatusReflectorExtension extension

Zero-overhead diagnostic reflector for Flutter DevTools and LPDAG inspection.
no setter
effect → Effect
The underlying Effect executed by this runner.
no setter
hashCode → int
The hash code for this object.
no setterinherited
isActive → bool
Whether both this runner and its underlying effect terminal are currently active.
no setteroverride
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited

Methods

noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
start() → void
Starts or resumes observation and execution of the underlying effect.
stop() → void
Stops or pauses observation and execution, relinquishing this runner's subscription edge.
toString() → String
A string representation of this object.
inherited

Operators

operator ==(Object other) → bool
The equality operator.
inherited