Composables
PulseHost
@Composable
fun <Broadcast : PulseBroadcast, Unicast : PulseUnicast> PulseHost(
container: PulseContainer<Broadcast, Unicast>,
content: @Composable (
onRefresh: () -> Unit,
onBroadcast: (Broadcast) -> Unit,
) -> Unit = { _, _ -> },
)Desktop only. Scopes a PulseContainer to this subtree. It emits no UI of its own and does two things: it publishes the Container key, so the PulseContent composables inside respond to container.refresh(), and it provides onRefresh and onBroadcast to the content block.
An app can contain several of them. In the demo, each destination that owns a Container hosts its own.
Parameters
| Parameter | Type | Description |
|---|---|---|
container | PulseContainer<Broadcast, Unicast> | The container to observe |
content | @Composable (onRefresh, onBroadcast) -> Unit | Content block receiving the two callbacks |
Example
PulseHost(container = appContainer) { onRefresh, onBroadcast ->
Column {
Button(onClick = { onBroadcast(AppBroadcast.Sync) }) {
Text("Sync All")
}
Button(onClick = { onRefresh() }) {
Text("Refresh View")
}
MyContent(viewModel = myViewModel)
}
}PulseContent
@Composable
fun <
State : PulseState,
Action : PulseAction,
Event : PulseEvent,
Broadcast : PulseBroadcast,
Unicast : PulseUnicast,
>
PulseContent(
viewModel: PulseViewModel<State, Action, Event, Broadcast, Unicast>,
onEvent: (Event) -> Unit = {},
content: @Composable (State, (Action) -> Unit) -> Unit = { _, _ -> },
)Observes a PulseViewModel and provides state and an action dispatcher to the content block. In mvi-mobile it takes the three-parameter PulseViewModel<State, Action, Event>, and it never re-creates its content on a refresh, since there is no PulseHost. It runs onSetup() the first time it observes an instance, but never cancels one.
Observe each instance from one PulseContent at a time. event is a single-consumer channel.
Parameters
| Parameter | Type | Description |
|---|---|---|
viewModel | PulseViewModel<State, Action, Event, Broadcast, Unicast> | The ViewModel to observe |
onEvent | (Event) -> Unit | Called for each one-time side effect emitted by the ViewModel |
content | @Composable (State, (Action) -> Unit) -> Unit | Renders the current state; receives a dispatcher to send actions |
Lifecycle behavior
PulseContentnever cancels the ViewModel; teardown belongs to whoever owns itLaunchedEffect(viewModel)collectsevent. It always delivers through the latestonEventpassed inonSetup()runs once, the first time aPulseContentobserves the ViewModel- The scope is cancelled when the owning
ViewModelStoreOwneris cleared - Leaving and re-entering composition — a navigation destination covering the route, for example — never repeats
onSetup() - Inside
PulseHost, the composable is wrapped inkey(containerKey). It re-creates onrefresh()
Example
PulseContent(
viewModel = counterViewModel,
onEvent = { event ->
when (event) {
is CounterEvent.ShowMessage ->
scope.launch { snackbarHostState.showSnackbar(event.message) }
}
},
) { state, onAction ->
Column {
Text("Count: ${state.count}")
Button(onClick = { onAction(CounterAction.Increment) }) {
Text("+")
}
}
}