Flows & Pulses
Air Framework uses a strict unidirectional data flow inspired by modern reactive architectures. This pattern is divided into two distinct concepts: Flows and Pulses.
Flows (Data)
Section titled “Flows (Data)”A Flow represents a stream of reactive data. It is the “State” that the UI observes.
- Nouns: Flows should be named after the data they represent (e.g.,
count,userProfile,items). - Reactive: When a Flow’s value changes, any
AirVieworAirBuilderlistening to it will rebuild automatically. - Read-Only (UI): From the UI perspective, Flows are read-only. You access their current state via the
.valueproperty.
// Generated Flow accessfinal currentCount = CounterFlows.count.value;Pulses (Actions)
Section titled “Pulses (Actions)”A Pulse is an event or action that triggers a change in the state. It is how the UI communicates back to the logic.
- Verbs: Pulses should be named as actions (e.g.,
increment,fetchData,submitForm). - Asynchronous: Pulses can be synchronous or asynchronous.
- Decoupled: Triggering a pulse doesn’t require knowing how the state will change, only what action is being requested.
// Triggering a Pulse (shorthand — callable syntax)onPressed: () => CounterPulses.increment(null),Callable Shorthand
Section titled “Callable Shorthand”AirPulse is a callable class. You can invoke it directly like a function — no need to call .pulse() explicitly:
// ✅ Shorthand (recommended)CounterPulses.increment(null);AuthPulses.login((email: email, password: password));
// ✅ Explicit form (also valid)CounterPulses.increment.pulse(null);Both forms are equivalent. The shorthand avoids the semantic redundancy of Pulses.x.pulse() while keeping the API expressive and clean.
The Cycle of Reactivity
Section titled “The Cycle of Reactivity”The interaction between Flows and Pulses creates a clean, predictable loop:
- Pulse: The UI triggers a Pulse (Action).
- Logic: The
AirControllerhandles the Pulse and updates its private fields. - Flow: The update automatically flows into the public
Flow(Data). - UI:
AirViewdetects the change and rebuilds the specific part of the screen.
graph LR
UI[UI Widget] -- Triggers --> Pulse[Pulse / Action]
Pulse -- Updates --> Controller[Controller Logic]
Controller -- Emits --> Flow[Flow / Data]
Flow -- Rebuilds --> UI
Why this pattern?
Section titled “Why this pattern?”- Testability: You can test Pulses and verify Flow outputs without a UI.
- Performance: High granularity means only the necessary widgets rebuild.
- Purity: No complex state transitions or hidden side effects. Just variables and actions.