Skip to content

Unicast ​

Unicast sends a typed message from a PulseViewModel up to its parent PulseContainer.

Use it when handling an action and coordinating the follow-up are split. The child ViewModel owns the immediate user action. The parent Container handles the follow-up, such as broadcasting the result to sibling ViewModels.

Defining a Unicast ​

Implement PulseUnicast with a sealed class whose cases are data classes or data objects. The Unicast type is shared by the Container and every ViewModel registered in it. This generic pairing means a ViewModel cannot emit a Unicast type that the Container does not understand.

kotlin
sealed class CounterUnicast : PulseUnicast {
    data class CounterUpdated(val count: Int) : CounterUnicast()
}
kotlin
class CounterViewModel : PulseViewModel<
    CounterState,
    CounterAction,
    CounterEvent,
    CounterBroadcast,
    CounterUnicast,
>(initialUiState = CounterState())
kotlin
class CounterContainer(
    viewModels: List<PulseViewModel<*, *, *, CounterBroadcast, CounterUnicast>>,
) : PulseContainer<CounterBroadcast, CounterUnicast>(viewModels = viewModels)

Sending a Unicast ​

Call unicast() from inside a ViewModel. PulseContainer collects each registered ViewModel's unicast flow internally.

kotlin
override fun onAction(uiAction: CounterAction) {
    when (uiAction) {
        CounterAction.Increment -> {
            repository.increment()
            unicast(CounterUnicast.CounterUpdated(repository.count.value))
        }
        CounterAction.Decrement -> {
            repository.decrement()
            unicast(CounterUnicast.CounterUpdated(repository.count.value))
        }
        CounterAction.Reset -> {
            repository.reset()
            unicast(CounterUnicast.CounterUpdated(repository.count.value))
        }
    }
}

Receiving a Unicast ​

Override onReceived() in the Container. In the example below, the Container takes the Unicast it received from a ViewModel and sends it back out to all ViewModels as a Broadcast.

kotlin
override fun onReceived(unicast: CounterUnicast) {
    when (unicast) {
        is CounterUnicast.CounterUpdated ->
            broadcast(CounterBroadcast.CounterUpdated(unicast.count))
    }
}

Unicast vs Broadcast vs Event ​

UnicastBroadcastEvent
DirectionViewModel -> ContainerContainer -> all ViewModelsViewModel -> UI
CardinalityMany-to-oneOne-to-manyOne-to-one
PurposeParent coordination after a child actionCross-ViewModel notificationOne-time UI side effects
Type parameterPulseUnicastPulseBroadcastPulseEvent

Example: Sharing Counter Updates ​

Two counter ViewModels keep separate local repositories, yet share updates through their parent Container:

kotlin
sealed class CounterBroadcast : PulseBroadcast {
    data class CounterUpdated(val count: Int) : CounterBroadcast()
}

sealed class CounterUnicast : PulseUnicast {
    data class CounterUpdated(val count: Int) : CounterUnicast()
}
kotlin
class CounterContainer(
    viewModels: List<PulseViewModel<*, *, *, CounterBroadcast, CounterUnicast>>,
) : PulseContainer<CounterBroadcast, CounterUnicast>(viewModels = viewModels) {
    override fun onReceived(unicast: CounterUnicast) {
        when (unicast) {
            is CounterUnicast.CounterUpdated ->
                broadcast(CounterBroadcast.CounterUpdated(unicast.count))
        }
    }
}
kotlin
class CounterViewModel(
    private val repository: CounterRepository,
) : PulseViewModel<CounterState, CounterAction, CounterEvent, CounterBroadcast, CounterUnicast>(
    initialUiState = CounterState(),
) {
    override fun onAction(uiAction: CounterAction) {
        when (uiAction) {
            CounterAction.Increment -> {
                repository.increment()
                unicast(CounterUnicast.CounterUpdated(repository.count.value))
            }
            CounterAction.Decrement -> {
                repository.decrement()
                unicast(CounterUnicast.CounterUpdated(repository.count.value))
            }
            CounterAction.Reset -> {
                repository.reset()
                unicast(CounterUnicast.CounterUpdated(repository.count.value))
            }
        }
    }

    override fun onReceive(broadcast: CounterBroadcast) {
        when (broadcast) {
            is CounterBroadcast.CounterUpdated -> repository.set(broadcast.count)
        }
    }
}

Data flow ​

Released under the Apache 2.0 License.