So, i have a composable, a viewmodel and a repository. I am fetching data from Firebase's Firestore, and it already has some documents in the collection i am fetching from.
These are the snippets of:
Composable:
var hint by remember {
mutableStateOf("")
}
LaunchedEffect (hint) {
Log.d("ViewModelScope issue", "SearchUsersScreen: LaunchedEffect triggered")
userChatViewModel.getAllUsersWithHint(hint)
}
Viewmodel:
val users = userRepository.users
// ...
fun getAllUsersWithHint(hint: String) {
Log.d("ViewModelScope issue", "Inside getAllUsersWithHint")
viewModelScope.launch {
try {
Log.d("ViewModelScope issue", "Inside getAllUsersWithHint's viewModelScope (start)")
userRepository.searchUsers(hint)
Log.d("ViewModelScope issue", "Inside getAllUsersWithHint's viewModelScope (finish)")
}
catch (e: Exception){
Log.e("ViewModelScope issue", "getAllUsersWithHint: \${e.message}")
}
}
}
Repository also contains logs and the function is surrounded with try catch, logging exceptions if any
I am using Koin for Dependency injection; the viewModel is lazily scoped (since it needs currently logged in user)
single<UserChatViewModel>(createdAtStart = false) { UserChatViewModel(get(), get()) }
Issue: When I visit this composable (I navigate to it using navcontroller) for the very first time, it works completely fine as shown in logs (happens everytime hint is changed)
SearchUsersScreen: LaunchedEffect triggered
Inside getAllUsersWithHint
Inside getAllUsersWithHint's viewModelScope (start)
Inside searchUsers (start)
Inside searchUsers (finish)
Inside getAllUsersWithHint's viewModelScope (finish)
But when the composable get removed out of backstack of navigation (I revisit again) the viewModelScope part just does not work
Logs (happens everytime hint is changed):
SearchUsersScreen: LaunchedEffect triggered
Inside getAllUsersWithHint
So the viewModelScope.launch block inside getAllUsersWithHint() never runs again. No exceptions are thrown either.
The issue was caused by how the ViewModel was declared in Koin. Using:
single<UserChatViewModel>(createdAtStart = false) { UserChatViewModel(get(), get()) }
creates a singleton ViewModel. When you first open the composable, it works fine. But when the composable is removed from the backstack, Jetpack Compose clears the ViewModel’s lifecycle, which cancels all coroutines in viewModelScope. Since the singleton instance remains, revisiting the composable uses the same ViewModel whose coroutine scope is already cancelled, so viewModelScope.launch never runs again.
Changing it to:
viewModelOf(::UserChatViewModel)
ties the ViewModel to the composable lifecycle. Compose clears the old ViewModel when navigating away and creates a new instance when revisiting. This ensures a fresh viewModelScope for coroutines, and your getAllUsersWithHint() function works every time.