sofnomic cr
sofnomic cr

Reputation: 322

How to navigate to another screen after call a viemodelscope method in viewmodel - Jetpack Compose

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

Answers (1)

FishHawk
FishHawk

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

Related Questions