PulseStore
abstract class PulseStore<
UiState : PulseState,
UiAction : PulseAction,
Event : PulseEvent,
Broadcast : PulseBroadcast,
>(
initialUiState: UiState,
)Abstract base class for managing the UI state of a single screen or section.
Properties
state
val state: StateFlow<UiState>The current UI state as a cold StateFlow. Calling onSetup() is triggered the first time this flow is collected.
currentState
val currentState: UiStateSynchronous snapshot of the current UI state. Equivalent to state.value.
event
val event: Flow<Event>A cold Flow of one-time side effects emitted via event(). Collected by PulseContent.
coroutineScope
val coroutineScope: CoroutineScopeA CoroutineScope backed by SupervisorJob + Dispatchers.Main + Dispatchers.IO. Cancelled and recreated on cancel().
Methods
onSetup()
open fun onSetup()Called once when state is first subscribed to (and again after each cancel()). Override to start data-collection coroutines.
onAction(uiAction)
abstract fun onAction(uiAction: UiAction)Called each time the UI dispatches an action. Launch coroutines for async work here.
onReceive(broadcast)
open fun onReceive(broadcast: Broadcast)Called when the parent PulseContainer delivers a broadcast. Default implementation does nothing.
update(block)
fun update(block: UiState.() -> UiState)Applies an immutable state update. The lambda receives the current state as this and must return the next state:
update { copy(count = count + 1) }event(effect)
fun event(effect: Event)Emits a one-time side effect to the UI layer. Collected by the onEvent lambda in PulseContent.
cancel()
fun cancel()Cancels the current coroutineScope and prepares the Store for reuse. Called automatically by PulseContent when it leaves the composition. After cancel(), the Store is ready to be re-subscribed (e.g., after a refresh()).
Example
class CounterStore(
private val repository: CounterRepository,
) : PulseStore<CounterState, CounterAction, CounterEvent, CounterBroadcast>(
initialUiState = CounterState(),
) {
override fun onSetup() {
coroutineScope.launch {
repository.count.collect { count ->
update { copy(count = count) }
}
}
}
override fun onAction(uiAction: CounterAction) {
coroutineScope.launch {
when (uiAction) {
CounterAction.Increment -> repository.increment()
CounterAction.Decrement -> repository.decrement()
}
}
}
override fun onReceive(broadcast: CounterBroadcast) {
when (broadcast) {
CounterBroadcast.Reset -> update { CounterState() }
}
}
}