I have shipped production Flutter apps for clinical tools, real-time monitoring systems, and complex ePRF platforms. In most projects, architecture that feels clean at week two becomes fragile by week twenty.
Provider, Riverpod, BLoC, and GetX are all strong tools. But for large products with layered business logic, branch-heavy state transitions, and interacting modules, many teams eventually blend data, logic, and event orchestration into hard-to-test flows.
The Problem at Scale
As apps grow, business logic leaks into widget callbacks, state holders become mixed with dependencies, and tests require deep provider setup. The issue is not API quality. The issue is weak architectural boundaries under delivery pressure.
| Concern | Common approach | Orchestra approach |
|---|---|---|
| State | Distributed across blocs/notifiers | Typed Component<T> entities |
| Logic | Mixed in widgets/providers | Stateless System classes |
| Events | Callbacks and ad-hoc streams | First-class Event and DataEvent |
| Modules | Global or nested scopes | Self-contained Orchestration units |
Core Concepts
Component<T>: Mutable typed state with observation.Event: Parameterless trigger for lifecycle or intent.DataEvent<T>: Trigger with strongly typed payload.Dependency<T>: Immutable injected configuration or service.
These entities are data-only by design. Logic belongs to systems.
Systems: Where Behavior Lives
Systems are stateless executors operating on entities. No internal mutable fields. This makes behavior deterministic and easy to unit test.
Reactive example
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);
}
}
Lifecycle Coverage
Orchestra models a full feature lifecycle with dedicated system types:
InitializeSystem: runs once on activationReactiveSystem: runs on watched entity/event changesExecuteSystem: runs every frame for continuous logicCleanupSystem: runs after execute passTeardownSystem: runs once on deactivation
Quick Start
The manual API makes architecture explicit; the Composer generator removes boilerplate. Both preserve strict typing and system isolation.
Install
# pubspec.yaml
dependencies:
orchestra: ^1.0.0
dev_dependencies:
orchestra_generator: ^1.0.0
build_runner: ^2.0.0
Run generator
dart run build_runner build
Testing Benefits
Because systems are stateless and orchestrations are self-contained, tests stay focused: trigger event, assert component state. No widget tree scaffolding required for business logic verification.
test('IncrementSystem increments counter', () {
final orchestrator = Orchestrator(
orchestrations: {CounterOrchestration()},
);
orchestrator.get<IncrementEvent>().trigger();
expect(orchestrator.get<CounterComponent>().value, 1);
});
Inspector DevTools
Orchestra includes an Inspector extension for Flutter DevTools, giving live visibility into entities, orchestration state, and system execution timing. This significantly shortens debugging loops in reactive workflows.
Why It Matters
In domains where architectural mistakes are expensive, explicit boundaries are not stylistic preferences. They are operational safeguards. Orchestra enforces those boundaries by type and lifecycle rather than convention alone.
Orchestra is available now on pub.dev, with source and documentation on GitHub.