Skip to content

Unicast ​

Unicast は、PulseViewModel から親の PulseContainer へ型付きメッセージを送る仕組みです。

使う場面は、操作の処理と後続の調整が分かれているときです。直近のユーザー操作は子の ViewModel が担当します。その結果を兄弟の ViewModel に Broadcast するといった調整は、親の Container が行います。

Unicast を定義する ​

PulseUnicast は sealed class で実装し、各ケースを data class か data object にします。Unicast の型は、Container とそこに登録されるすべての ViewModel で共有されます。このジェネリクスの組み合わせにより、Container が理解できない Unicast 型を ViewModel が発行することはできません。

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)

Unicast を送る ​

ViewModel の中から unicast() を呼びます。PulseContainer は登録済みの各 ViewModel の unicast Flow を内部で収集します。

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))
        }
    }
}

Unicast を受け取る ​

Container で onReceived() をオーバーライドします。次の例では、Container が ViewModel から受け取った Unicast を、ViewModel 群への Broadcast として送り直しています。

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

Unicast・Broadcast・Event の違い ​

UnicastBroadcastEvent
方向ViewModel -> ContainerContainer -> すべての ViewModelViewModel -> UI
多重度多対 11 対多1 対 1
目的子の操作後に親が行う調整ViewModel 横断の通知一度きりの UI 副作用
型パラメータPulseUnicastPulseBroadcastPulseEvent

例: カウンターの更新を共有する ​

2 つのカウンター ViewModel はそれぞれ別のリポジトリを持ちますが、親の 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)
        }
    }
}

データの流れ ​

Apache 2.0 License の下で公開されています。