orchestra 1.0.2 copy "orchestra: ^1.0.2" to clipboard
orchestra: ^1.0.2 copied to clipboard

Reactive state management for Dart and Flutter. Clean architecture, deterministic logic, and built-in DevTools Inspector.

Orchestra #

pub package Apache 2.0 License Flutter Dart

A reactive state management library for Dart and Flutter. Promotes clean architecture, deterministic business logic, and scalable application design through strict separation of data, behavior, and events.


Why Orchestra #

Concern Traditional approach Orchestra
State Scattered across notifiers / blocs Typed Component<T> entities
Logic Coupled to widgets or providers Stateless System classes
Events Callbacks / streams passed through the tree First-class Event / DataEvent entities
Modules Global providers or nested scopes Self-contained Orchestration
Debugging Ad-hoc logging Built-in Inspector DevTools extension

Core Concepts #

Entities #

Everything that holds data or represents an occurrence is an entity.

Type Description
Component<T> Holds mutable state. Notifies listeners on change.
Event A trigger with no payload.
DataEvent<T> A trigger that carries typed data.
Dependency<T> An immutable value injected into systems.

Systems #

Systems contain all business logic. They are stateless and operate on entities.

Type When it runs
InitializeSystem Once, after the orchestration activates
ReactiveSystem When a watched entity changes or an event fires
ExecuteSystem Every frame (ticker-driven)
CleanupSystem After every execute pass
TeardownSystem Once, when the orchestration is deactivated

Orchestration #

Groups related entities and systems into a cohesive, self-contained module.

Orchestrator #

Central coordinator. Manages one or more orchestrations, drives system execution, and provides type-based entity lookup across the whole graph.


Installation #

dependencies:
  orchestra: ^1.0.0

# Optional: add if you want to use the Composer declarative API
dev_dependencies:
  orchestra_generator: ^1.0.0
  build_runner: ^2.0.0

Quick Start #

There are two ways to use Orchestra:

  • Manual — subclass Component, Event, System, and Orchestration directly (shown below).
  • Code generation — declare everything with Composer and let orchestra_generator generate the classes. See Code Generation.

1. Define entities #

// Component — holds state
class CounterComponent extends Component<int> {
  CounterComponent() : super(0);
}

// Event — parameterless trigger
class IncrementEvent extends Event {}

// DataEvent — trigger with payload
class SetCounterEvent extends DataEvent<int> {}

2. Write systems #

class IncrementSystem extends ReactiveSystem {
  @override
  Set<Type> get reactsTo => {IncrementEvent};

  @override
  Set<Type> get interactsWith => {CounterComponent};

  @override
  void react() {
    final counter = get<CounterComponent>();
    counter.update(counter.value + 1);
  }
}

class SetCounterSystem extends ReactiveSystem {
  @override
  Set<Type> get reactsTo => {SetCounterEvent};

  @override
  Set<Type> get interactsWith => {CounterComponent};

  @override
  void react() {
    final event = get<SetCounterEvent>();
    final counter = get<CounterComponent>();
    counter.update(event.data);
  }
}

3. Bundle into an orchestration #

class CounterOrchestration extends Orchestration {
  CounterOrchestration() {
    add(CounterComponent());
    add(IncrementEvent());
    add(SetCounterEvent());
    add(IncrementSystem());
    add(SetCounterSystem());
  }
}

4. Wire up the orchestrator #

final orchestrator = Orchestrator(
  orchestrations: {CounterOrchestration()},
);

// Trigger events
orchestrator.get<IncrementEvent>().trigger();
orchestrator.get<SetCounterEvent>().trigger(42);

// Read state
print(orchestrator.get<CounterComponent>().value); // 42 + 1 = 43

Entity API #

Component #

final counter = orchestrator.get<CounterComponent>();

counter.update(10);         // update + notify listeners
counter.update(10, notify: false); // silent update
counter.update(10, force: true);   // update even if value is equal
counter.value = 20;         // shorthand for update(20)

print(counter.value);       // current value
print(counter.previous);    // previous value after last update
print(counter.updatedAt);   // DateTime of last update

Event #

final increment = orchestrator.get<IncrementEvent>();
increment.trigger();
print(increment.triggeredAt); // DateTime of last trigger

DataEvent #

final setCounter = orchestrator.get<SetCounterEvent>();
setCounter.trigger(99);

print(setCounter.data);       // 99 — throws if never triggered
print(setCounter.dataOrNull); // 99? — null if never triggered
print(setCounter.triggeredAt);

Dependency #

// Define
class ApiUrlDependency extends Dependency<String> {
  ApiUrlDependency(super.value);
}

// Register in an orchestration
add(ApiUrlDependency('https://api.example.com'));

// Access in a system
final url = get<ApiUrlDependency>().value;

System API #

ReactiveSystem #

class UpdateCacheSystem extends ReactiveSystem {
  @override
  Set<Type> get reactsTo => {UserComponent, AuthEvent};

  @override
  Set<Type> get interactsWith => {CacheComponent};

  // Optional guard — skip react() when false
  @override
  bool get reactsIf => get<AuthComponent>().value.isLoggedIn;

  @override
  void react() {
    // called once per qualifying entity change
  }
}

ExecuteSystem #

class AnimationSystem extends ExecuteSystem {
  @override
  Set<Type> get interactsWith => {AnimationComponent};

  @override
  bool get executesIf => get<AnimationComponent>().value.isPlaying;

  @override
  void execute(Duration elapsed) {
    final anim = get<AnimationComponent>();
    anim.update(anim.value.advance(elapsed));
  }
}

InitializeSystem / TeardownSystem #

class DatabaseInitSystem extends InitializeSystem {
  @override
  Set<Type> get interactsWith => {DatabaseComponent};

  @override
  void initialize() {
    get<DatabaseComponent>().update(Database.open());
  }
}

class DatabaseTeardownSystem extends TeardownSystem {
  @override
  void teardown() {
    get<DatabaseComponent>().value.close();
  }
}

Logging from systems and entities #

// Inside any System
log('Processing user data');
log('Entity null — skipping', level: LogLevel.warning);

// Inside any Entity
log('Value sanitized before update');

Orchestra includes a declarative API via Composer that eliminates boilerplate. Instead of writing entity and system classes by hand, you declare your orchestration inline and let orchestra_generator generate all the class definitions at build time.

Composer is a code-generation DSL, not a runtime API. It requires orchestra_generator and build_runner to produce the actual Dart classes. See the orchestra_generator README for full documentation.

// counter_feature.dart
import 'package:orchestra/orchestra.dart';

part 'counter_feature.g.dart'; // generated output

final counterFeature = Composer.createOrchestration();

final counter   = counterFeature.addComponent(0);
final increment = counterFeature.addEvent();
final setTo     = counterFeature.addDataEvent<int>();

final onIncrement = counterFeature.addReactiveSystem(
  reactsTo: {increment},
  react: () {
    counter.value = counter.value + 1;
  },
);

final onSetTo = counterFeature.addReactiveSystem(
  reactsTo: {setTo},
  react: () {
    counter.value = setTo.data;
  },
);

Run the generator, and counter_feature.g.dart is produced with all entity classes, system classes, and the CounterFeatureOrchestration wired up automatically.

dart run build_runner build

Orchestration Lifecycle #

Lifecycle diagram


Testing #

Unit-testing a system #

test('IncrementSystem increments counter', () {
  final orchestration = CounterOrchestration();
  final orchestrator = Orchestrator(orchestrations: {orchestration});

  orchestrator.get<IncrementEvent>().trigger();

  expect(orchestrator.get<CounterComponent>().value, 1);
});

Testing reactive behavior #

test('system reacts to event', () {
  final orchestration = CounterOrchestration();
  final orchestrator = Orchestrator(orchestrations: {orchestration});
  orchestrator.initialize();

  orchestrator.get<SetCounterEvent>().trigger(55);

  expect(orchestrator.get<CounterComponent>().value, 55);
});

Testing components in isolation #

test('component tracks previous value', () {
  final component = CounterComponent();

  component.update(10);
  component.update(20);

  expect(component.value, 20);
  expect(component.previous, 10);
});

test('component skips equal values', () {
  final component = CounterComponent();
  int notifyCount = 0;

  component.addListener(TestListener(() => notifyCount++));
  component.update(0); // equal — skipped

  expect(notifyCount, 0);
});

Inspector DevTools Extension #

Orchestra ships with a built-in DevTools extension. When running in debug mode, open Flutter DevTools and navigate to the Orchestra tab to inspect:

  • All active orchestrators and orchestrations
  • Live entity state and change history
  • System execution logs and timing
  • Reactive dependency graph

No setup required — the extension registers automatically on first Orchestrator construction.


License #

Apache License 2.0 — see the LICENSE file for details.

Copyright 2026 Ehsan Rashidi


Issues: github.com/FlameOfUdun/orchestra/issues Discussions: github.com/FlameOfUdun/orchestra/discussions

0
likes
160
points
--
downloads

Documentation

API reference

Publisher

verified publisherwinchetechnologies.co.uk

Weekly Downloads

Reactive state management for Dart and Flutter. Clean architecture, deterministic logic, and built-in DevTools Inspector.

Repository (GitHub)
View/report issues

Topics

#state-management #architecture #reactive #devtools

License

Apache-2.0 (license)

More

Packages that depend on orchestra