概要
viewModelScopeは、画面のための処理を実行する場所です。画面表示やユーザー操作に応じた処理のように、画面が閉じたら止まっても問題ないものは、viewModelScopeに置くのが自然です。
一方で、画面の外でも効いていてほしい処理や、途中で止まると困る処理は注意が必要です。そうした処理までviewModelScopeに載せると、起動元がたまたまViewModelだったというだけで、画面破棄に処理の寿命が引きずられてしまいます。
良い例
- 画面表示に必要なデータ取得。(画面内に影響が閉じているので、キャンセルされても問題ない)
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
fun loadUser(userId: String) {
viewModelScope.launch {
val user = repository.fetchUser(userId)
// UI state を更新
}
}
}
注意が必要な例
- アプリ全体で使う同期処理を特定画面のViewModelから直接起動する場合
class SomeViewModel : ViewModel() {
fun syncSharedState() {
viewModelScope.launch {
sharedStateSynchronizer.sync()
}
}
}
このsyncSharedState()が単なる表示更新のためではなく、ログイン状態に応じた共通データの再同期、複数画面から参照されるキャッシュの整合、未送信データの反映など、アプリ全体で前提となる状態を更新する処理だとします。
その場合、この処理は「この画面が表示されている間だけ生きていればよい仕事」ではありません。にもかかわらずviewModelScopeで起動すると、その画面が閉じられた時点で処理もキャンセルされます。
問題なのは、処理が途中で止まること自体ではなく、「途中で止まってよい性質の仕事なのか」が画面の寿命で決まってしまうことです。本来は画面の表示有無と無関係に完了してほしい同期処理なのに、たまたま起動元がViewModelだったために、画面破棄で止まる設計になってしまいます。
つまり、このケースで確認すべきなのは、syncSharedState()がUI Stateを更新するための補助処理なのか、それともアプリ全体の整合性を保つ責務を持つ処理なのか、という点です。後者であれば、特定画面のViewModelに寿命を委ねるのは適切でなく、画面の破棄とは独立した実行基盤で扱うべきです。