PulseViewModel
abstract class PulseViewModel<
UiState : PulseState,
UiAction : PulseAction,
Event : PulseEvent,
Broadcast : PulseBroadcast,
Unicast : PulseUnicast,
>(
initialUiState: UiState,
coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default,
)1 つの Content の UI 状態を管理する抽象基底クラスです。
親の Container へメッセージを送る必要があるときは unicast() を使います。
プロパティ
state
val state: StateFlow<UiState>現在の UI 状態を表す、読み取り専用のホットな StateFlow です。収集しても ViewModel のライフサイクルは変わりません。
currentState
val currentState: UiState現在の UI 状態の同期的なスナップショットです。state.value と同じです。
event
val event: Flow<Event>event() で発行された一度きりの副作用の、コールドな Flow です。PulseContent が収集します。各 Event は 1 つの収集者に届いて消費され、後から来た収集者に再生されることはありません。
unicast
val unicast: SharedFlow<Unicast>unicast() で発行された、子から親への Unicast のホットなストリームです。
coroutineScope
val coroutineScope: CoroutineScopeSupervisorJob と、コンストラクタに渡されたディスパッチャに支えられた CoroutineScope です。ディスパッチャの既定値は Dispatchers.Default で、Dispatchers.Main やテスト用ディスパッチャに差し替えられます。
cancel() を呼ぶと、このスコープはキャンセルされ、新しいものに作り直されます。
メソッド
onSetup()
open fun onSetup()PulseContent が ViewModel を最初に観測したときに、一度だけ呼ばれます。インスタンスはコンポジションより長く生きるので、コンポジションを離れて戻ってきても繰り返されません。
データ収集のコルーチンを開始するためにオーバーライドしてください。コルーチンは coroutineScope で動き、スコープがキャンセルされると止まります。
1 つのインスタンスは、同時に 1 つの PulseContent からだけ観測してください。event は単一消費者のチャネルなので、2 つ目の観測者がいると、最初の観測者に届かない Event が出てきます。
onAction(uiAction)
abstract fun onAction(uiAction: UiAction)UI が Action を発行するたびに呼ばれます。非同期処理はここでコルーチンを起動してください。
onReceive(broadcast)
open fun onReceive(broadcast: Broadcast)親の PulseContainer が Broadcast を届けたときに呼ばれます。既定の実装は何もしません。
update(block)
fun update(block: UiState.() -> UiState)不変な状態更新を適用します。ラムダは現在の状態を this として受け取り、次の状態を返します。
update { copy(count = count + 1) }event(effect)
fun event(effect: Event)一度きりの副作用を UI 層へ発行します。PulseContent の onEvent ラムダが収集します。
バッファリングされるため中断せず、発行順序も保たれます。
PulseContent が収集していない間(別の destination に覆われている間など)に発行された Event は、バッファで待機し、収集者が戻ってきたときに届きます。バッファは 64 件で、それを超えると最も古い Event が捨てられます。
unicast(unicast)
fun unicast(unicast: Unicast)子から親へのメッセージを発行します。親の PulseContainer が ViewModel の unicast Flow を収集し、onReceived() で受け取ります。
cancel()
fun cancel()onSetup() で開始した処理をキャンセルし、状態を保ったままスコープを新しいものに置き換えます。次にこのインスタンスを観測した PulseContent が、onSetup() を再実行します。ViewModel が再び有効になる可能性があるときに使ってください。
close()
fun close()onSetup() で開始した処理を完全にキャンセルします。cancel() と違い、スコープは置き換えません。所有する ViewModelStore が破棄されるときに、onCleared() がこれを呼びます。そのため、破棄された ViewModel がそれより長く生きる処理を起動することはありません。
例
sealed class CounterUnicast : PulseUnicast {
data object ResetRequested : CounterUnicast()
}
class CounterViewModel(
private val repository: CounterRepository,
) : PulseViewModel<CounterState, CounterAction, CounterEvent, CounterBroadcast, CounterUnicast>(
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() }
}
}
}sealed class CounterUnicast : PulseUnicast {
data object ResetRequested : CounterUnicast()
}
class CounterViewModel(
private val repository: CounterRepository,
) : PulseViewModel<CounterState, CounterAction, CounterEvent, CounterBroadcast, CounterUnicast>(
initialUiState = CounterState(),
) {
override fun onAction(uiAction: CounterAction) {
when (uiAction) {
CounterAction.Reset -> {
repository.reset()
unicast(CounterUnicast.ResetRequested)
}
CounterAction.Increment -> repository.increment()
CounterAction.Decrement -> repository.decrement()
}
}
}