Skip to content

Architecture

PulseMVI follows the MVI (Model-View-Intent) pattern and adds two Desktop-specific primitives: Broadcast and View Refresh.

Data Flow

┌─────────────────────────────────────────────────────┐
│                   Compose UI                        │
│                                                     │
│   User Interaction                                  │
│        │                                            │
│        ▼                                            │
│   onAction(action)  ──────────▶  PulseStore         │
│                                      │              │
│                               onAction()            │
│                                      │              │
│                               update { }            │
│                                      │              │
│                            StateFlow<State>         │
│                                      │              │
│        ◀──────────────────────────── │              │
│   PulseContent re-renders            │              │
│                                      │              │
│                               event(effect)         │
│                                      │              │
│        ◀──────────── onEvent ──────── │              │
│   Handle side effect                                │
└─────────────────────────────────────────────────────┘

Broadcast Flow

When multiple Stores need to react to the same event, use PulseContainer.broadcast():

Container.broadcast(MyBroadcast.Sync)

        ├──▶ StoreA.onReceive(Sync)  ──▶ update { }  ──▶ UI re-renders

        └──▶ StoreB.onReceive(Sync)  ──▶ update { }  ──▶ UI re-renders

View Refresh Flow

Container.refresh() forces the Compose view tree to reconstruct. Store states are preserved — only the Composables are re-created:

Container.refresh()

        └──▶ PulseApp detects new key

                  └──▶ PulseContent re-created (via `key()`)

                            └──▶ Store.cancel() then Store re-subscribes

                                      └──▶ onSetup() called again

Component Responsibilities

ComponentResponsibility
PulseStateImmutable snapshot of UI data
PulseActionUser intent — what the user wants to do
PulseEventOne-time side effect — navigation, dialog, snackbar
PulseBroadcastCross-Store notification from Container
PulseStoreOwns state; handles actions and broadcasts
PulseContainerCoordinates Stores; enables broadcast and refresh
PulseAppCompose wrapper that propagates container key
PulseContentCompose wrapper that observes a Store

Lifecycle

PulseContent appears

        └──▶ Store.state subscribed  ──▶  onSetup() called

                                        coroutineScope active

PulseContent disappears

        └──▶ Store.cancel() called

                  └──▶ coroutineScope cancelled + recreated
                            (Store is ready to be reused)

TIP

onSetup() is called every time the Store is first subscribed to — including after a refresh(). Use it to start your data-collection coroutines.

Released under the Apache 2.0 License.