Reputation: 322
Hello developers i have a problem i calling an api from viewmodel but is a coroutine and i do not how to wait to api response to make login .... i have this method in viewmodel:
fun createUser(context: Context){
viewModelScope.launch {
val jsonObject= JSONObject()
jsonObject.put("correo", register.value.correo)
jsonObject.put("password", register.value.password)
jsonObject.put("nombre", register.value.nombre)
jsonObject.put("apellido", register.value.apellido)
jsonObject.put("direccion",register.value.direccion)
jsonObject.put("telefono", register.value.telefono)
var bodyrequest=jsonObject.toString().toRequestBody()
val result: RegisterResponse = registerService.register(bodyrequest)
response.value=result
Log.d("response",result.message)
}
}
and this is onClick event from button in a @Composable function:
onClick = {
if (vm.validateCredentials()=="ok"){
vm.createUser(context)
Log.d("result from compose","${vm.response.value.message}")
navController.navigate(Screen.login.route){
popUpTo(Screen.login.route){
inclusive=true
}
}
}else{
showPopUp = !showPopUp
}
}
The big problem is that when the navigation is triggered, the coroutine has not finished yet and therefore I cannot find out if the api response was successful or not. How could I do this?
Upvotes: 1
Views: 1266
Reputation: 474
You can let createUser
return the Job
, and then join
it in onClick
. Like this:
fun createUser(context: Context) = viewModelScope.launch {...}
...
onClick = {
scope.launch {
viewModel.createUser(context).join()
navController.navigate(Screen.login.route){
popUpTo(Screen.login.route){
inclusive=true
}
}
}
}
But this makes the code difficult to maintain. I suggest having the ViewModel layer send an Event
when createUser
is done and the View layer collects it and navigate to a new screen. Like this:
interface Event
abstract class BaseViewModel<E : Event> : ViewModel() {
private val _event = Channel<E>()
val event = _event.receiveAsFlow().shareIn(viewModelScope, SharingStarted.Lazily)
protected suspend fun sendEvent(event: E) = _event.send(event)
protected fun sendEventSync(event: E) = viewModelScope.launch { _event.send(event) }
}
@Composable
fun <E : Event> OnEvent(event: Flow<E>, onEvent: (E) -> Unit) {
LaunchedEffect(Unit) {
event.collect(onEvent)
}
}
sealed interface LoginEvent : Event {
object LoginSuccess : LoginEvent
data class LoginFailure(val exception: Throwable) : LoginEvent
}
class LoginViewModel() : BaseViewModel<LoginEvent>() {
fun createUser(context: Context){
viewModelScope.launch {
repository.loign()
.onSuccess { sendEvent(LoginEvent.LoginSuccess) }
.onFailure { sendEvent(LoginEvent.LoginFailure(it)) }
}
}
}
@Composable
fun Screen() {
...
OnEvent(viewModel.event) {
when (it) {
LoginEvent.LoginSuccess ->
navController.navToXXXScreen()
is LoginEvent.LoginFailure ->
context.toast(it.exception.localizedMessage ?: "")
}
}
}
Upvotes: 5