ViewModel

The ViewModel Class and viewModelScope

A ViewModel (androidx.lifecycle:lifecycle-viewmodel-compose 2.11.0) lives in a ViewModelStore owned by an activity or a navigation back-stack entry. The store survives configuration changes, and the ViewModel is cleared, with a call to onCleared(), only when its owner finishes for good. Each ViewModel carries a viewModelScope, a CoroutineScope on Dispatchers.Main.immediate that is cancelled when the ViewModel is cleared, which is exactly the lifetime a screen's background work should have. BookNest's catalog loads through a BookRepository whose suspend fun loadBooks() parses books.json on Dispatchers.IO after a one-second delay standing in for the network:

ui/ViewModels.kt: CatalogViewModel loads once per ViewModel (excerpt)Kotlin
class CatalogViewModel(private val repo: BookRepository) : ViewModel() {
  private val load = MutableStateFlow<LoadState<List<Book>>>(LoadState.Loading)
  ...
  init {
    Log.d(TAG, "CatalogViewModel created")
    refresh()
  }
  fun refresh() {
    viewModelScope.launch {                              // cancelled in onCleared()
      load.value = LoadState.Loading
      load.value = try {
        LoadState.Success(repo.loadBooks())
      } catch (e: IOException) {
        LoadState.Error(e.message ?: "Could not load books")
      }
      Log.d(TAG, "loaded: ${load.value::class.simpleName}")
    }
  }
  ...
  companion object {
    val Factory = viewModelFactory {
      initializer { CatalogViewModel(BookRepository(this[APPLICATION_KEY]!!.assets)) }
    }
  }
}

The framework creates ViewModels, so constructor arguments need a factory: viewModelFactory { initializer { } } builds one, and APPLICATION_KEY fetches the Application (Hilt and Koin replaces this with dependency injection). Never hand a ViewModel an Activity, View or composable Context: it outlives them and would leak the old activity.