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:
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.