Closed cryptrr closed 1 year ago
here sample with compose multiplatform & moko-mvvm - https://github.com/icerockdev/moko-compose-multiplatform-template/tree/main try to reproduce issue in this repo.
maybe your viewmodel already call onCleared
and viewModelScope
already disposed
Thanks, let me look into it.
This is what I get when I log viewModelScope
CoroutineScope(coroutineContext=[JobImpl{Cancelled}@5f81b7, Dispatchers.Main])
Yeah viewModelScope.isActive is false
onCleared() is getting called instantly on navigation to the screen
set breakpoint to onCleared and check what code call it in stacktrace
I investigated it and got another very weird behaviour.
The viewModel is being cleared when using conditional rendering even when viewModel is initialized in the parent composable.
This is from another screen's viewModel that is for some reason not automatically getting cleared on page load
@Composable
fun LoginScreen(){
val viewModel = getViewModel(key = Unit, factory = viewModelFactory{ LoginScreenViewModel() })
val loginState by viewModel.stateFlow.collectAsState(initial = Result.None)
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
when (loginState) {
is Result.Loading -> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator(color = Color.Blue)
}
}
is Result.None -> {
LoginContent()
}
else -> { }
}
}
}
@Composable
fun LoginContent(){
//Is also using the same viewModel initialized in the parent.
val viewModel = getViewModel(key = Unit, factory = viewModelFactory{ LoginScreenViewModel() })
Button(onClick = {viewModel.login(viewModel.emailFieldText)}){
//Button Content
}
}
The function viewModel.login() changes the loginState to Result.Loading and hence the parent composable's when
shows the CircularProgressIndicator
Now the viewModel is getting cleared even though the viewModel is initialized in the parent composable ie LoginScreen()
If you are not using conditional rendering in LoginScreen()
using when()
, the viewModel is not cleared.
you should pass viewmodel as argument to inner composable.
because getViewModel
works on remember
logic. when you use getViewModel
in some different Composable but with same key - you can got unexpected results like this.
That fixed it but the original problem remains.
Here's the composable code for that screen
@Composable
fun ForgotPasswordContent(){
val viewModel = getViewModel(key = Unit, factory = viewModelFactory{ ResetPasswordViewModel() })
val resetPasswordRequestState by viewModel.stateFlow.collectAsState(initial = Result.None)
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
FullWidthButton(
"Submit",
UIBlue,
isLoading = resetPasswordRequestState is Result.Loading,
onClick = {
viewModel.sendResetPasswordRequest(viewModel.emailFieldText)
}) {}
}
}
Here's the full ViewModel
class ResetPasswordViewModel : ViewModel(), KoinComponent {
val main: MainViewModel by inject()
var emailFieldText by mutableStateOf("")
private val _stateFlow = MutableStateFlow<Result<SimpleResponseDTO>>(value = Result.None)
val stateFlow = _stateFlow.asSharedFlow()
override fun onCleared() {
//OnCleared is called instantly on screen render
Logger.d{"Clearing viewmodel"}
super.onCleared()
}
fun sendResetPasswordRequest(email: String){
Logger.d { "is getting logged" }
Logger.d { viewModelScope.isActive.toString() }
Logger.d { viewModelScope.coroutineContext.toString() }
viewModelScope.launch{
//Not running at all
_stateFlow.value = Result.Loading
delay(2000L)
Logger.d { "is not getting logged" }
_stateFlow.value = Result.None
}
}
}
This is the full stack trace of onCleared()
Okay it's fine. I thought key was used for recomposition of ViewModel ala LaunchedEffect and put the same key everywhere.
Tried changing Dispatchers but none works.
Moko dependencies used in Common: