MVI
The library follows the MVI (Model-View-Intent) pattern, and the data flow below is the same on the desktop and on mobile. A desktop window adds three ways for its ViewModels to work together — Broadcast, Unicast and View Refresh — through a Container. Mobile has no Container, so the sections from Broadcast Flow to View Refresh Flow apply to the desktop only.
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 (desktop) |
PulseUnicast | Child-to-parent notification from ViewModel (desktop) |
PulseViewModel | Owns state; handles actions and broadcasts; can emit unicasts |
PulseContainer | Coordinates ViewModels; enables broadcast, unicast handling, and refresh (desktop) |
PulseHost | Compose wrapper that propagates container key (desktop) |
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.