Architecture
PulseDesktop follows the MVI (Model-View-Intent) pattern. It adds three coordination primitives: Broadcast, Unicast, and View Refresh.
Data Flow
Broadcast Flow
When multiple ViewModels need to react to the same event, use PulseContainer.broadcast():
Unicast Flow
When a child ViewModel needs to notify its parent Container, use PulseViewModel.unicast():
View Refresh Flow
Container.refresh() forces the Compose view tree to reconstruct. ViewModel states are preserved; only the Composables are re-created:
Component Responsibilities
| Component | Responsibility |
|---|---|
PulseState | Immutable snapshot of UI data |
PulseAction | User intent — what the user wants to do |
PulseEvent | One-time side effect — navigation, dialog, snackbar |
PulseBroadcast | Cross-ViewModel notification from Container |
PulseUnicast | Child-to-parent notification from ViewModel |
PulseViewModel | Owns state; handles actions and broadcasts; can emit unicasts |
PulseContainer | Coordinates ViewModels; enables broadcast, unicast handling, and refresh |
PulseHost | Compose wrapper that propagates container key |
PulseContent | Compose wrapper that observes a ViewModel |
Lifecycle
TIP
onSetup() runs once, when PulseContent first observes the ViewModel. The ViewModel stays active for as long as its ViewModelStoreOwner lives. A composition restart never repeats setup. Neither does refresh().
Which owner that is decides the ViewModel's lifetime.
- Created under the host owner, it stays alive for the whole screen
- Created inside a Navigation 3 destination, it is scoped to that back stack entry: covered by another destination it is kept, and popping the route cancels it
The demo builds every destination the second way. See Navigation 3.