2

In my Viewmodel class I do the next code:

 init {
    viewModelScope.launch(Dispatchers.IO) {
        val homepageItemsCall = async { getHomepageItems() }
        val carouselItemsCall = async { getCarouselItems() }
        
        homepageItemsCall.await()
        carouselItemsCall.await()

        checkFavoriteStatus(homeItemsTest)
        carouselItems.postValue(carouselItemsTest)

    }
}

This is how my homepageItemsCall looks:

 private fun getHomepageItems() = viewModelScope.launch(Dispatchers.IO) {
    restService.getHomepage().getResult(
        success = { value ->
            homeItemsTest = value
        },
        genericError = { _, message ->
            error.postValue(message)
        },
        networkError = {
            error.postValue(TranslationManager.getString(TKeys.LOGIN_NETWORK_ERROR))
        }
    )
}

My expectation was like this:

  1. I create a coroutine on ViewmodelScope that is going to execute code in init block.
  2. since I am using async await, my code will not be executed until my API calls are done. This means both of my API calls will go to success/failure and after that, my code can go to the next line: "checkFavoriteStatus(homeItemsTest)".

But it doesn't work like that. Program goes to checkFavoriteStatus(homeItemsTest) line before my API calls are done even though I used async await. I thought that async await suspends/blocks coroutine that is executing async code (in this case, coroutine that is executing my whole init block..? Did I get something wrong?

And if yes, what is the best way to wait for my API calls to finish and then go to the next code by using coroutines?

Edit these are getHomePage and getResult functions:

   suspend fun <T> ResultWrapper<T>.getResult(
    success: suspend (value: T) -> Unit,
    genericError: (suspend (code: Int?, message: String?) -> Unit)? = 
  null,
    networkError: (suspend () -> Unit)? = null,
    error: (suspend () -> Unit)? = null
    ) {

    when (this) {
        is ResultWrapper.Success -> {
            success(value)
        }
        is ResultWrapper.GenericError -> {
            genericError?.let { it(code, errorMessage) }
        }
        is ResultWrapper.NetworkError -> {
            networkError?.let { it() }
        }
    }

    if (this is ResultWrapper.NetworkError || this is ResultWrapper.GenericError)
        error?.let { it() }
  }

       suspend fun getHomepage() = safeApiCall(Dispatchers.IO) {
    apiClient.getHomepageElements()
}
Kratos
  • 681
  • 1
  • 13
  • 30
  • Is `restService.getHomepage()` or `.getResult()` suspend? If they are not getHomepageItems function will return immediately, which you probably don't want here. – Arpit Shukla Dec 15 '21 at 12:34
  • @ArpitShukla yes they are both suspend functions – Kratos Dec 16 '21 at 08:05
  • @Kratos Oh, that's weird. Usually, there is only one operation that waits for the network and it's either suspend or based on callbacks. In your case it seems like there are 3 distinct places in the code where waiting happens. What is this rest client? Is `getHomepage()` and `getResult()` provided by the library or implemented by you? Could you share their source code? – broot Dec 16 '21 at 09:43
  • @broot of course no problem. Take a look I added it. GetResult is an extension function and we added it for better error handling. GetHomePage is standard function in ViewModel. – Kratos Dec 16 '21 at 11:23
  • So, first of all, you probably provided a different, similarly named function: `GetHomePage()` instead of `restService.getHomepage()`. Secondly, there is a weird habit in this code to mark all functions as `suspend`, even if they don't really suspend and start every function with `launch()`. `launch()` makes it impossible / much harder to track the operation, wait for it to finish and get its results. – broot Dec 16 '21 at 12:18
  • Your initial question was to how to wait for the operation to finish, but your whole codebase is designed in a way that makes this impossible. You need to redesign your code, remove all these `launch()` wherever they are not needed / undesired. – broot Dec 16 '21 at 12:20
  • Ahh, `getResult()` also makes things harder, not easier. Assuming that you have a variable with `ResultWrapper` in it, how do you suppose to for example return its success value or null? You can't, because you have to provide a callback, so returning the value is tricky. You can at least make this function `inline`, so it will be possible to `return` from callbacks. – broot Dec 16 '21 at 12:24
  • You may want to learn how to [Convert callback style to suspend function](https://stackoverflow.com/questions/48552925/existing-3-function-callback-to-kotlin-coroutines/48562175#48562175). – Ricky Mo Dec 20 '21 at 05:59

2 Answers2

0

If you want to do something after your API call is completed you can do something like this

init {
    viewModelScope.launch(Dispatchers.IO) {
      homepageItemsCall =  getHomepageItems()
      carouselItemsCall =  getCarouselItems() 
        
    }.invokeOnCompletion{
        checkFavoriteStatus(homeItemsTest)
        carouselItems.postValue(carouselItemsTest)
      }
  }
End User
  • 792
  • 6
  • 14
  • I tried your solution, but had to add call in coroutine in invokeOnCompletion since my checkFavoriteStatus is suspend fun so must be called from coroutine. But it still behaves the same – Kratos Dec 16 '21 at 08:04
  • What is `invokeOnCompletion`? – IgorGanapolsky Aug 23 '22 at 17:48
  • Registers handler that is synchronously invoked once on completion of this job. When the job is already complete, then the handler is immediately invoked with the job's exception or cancellation cause or null. Otherwise, the handler will be invoked once when this job is complete – End User Aug 24 '22 at 08:17
0

Try to make function getHomepageItems and getCarouselItems to suspend, like this:

private suspend fun getHomepageItems() {
    restService.getHomepage().getResult(
        success = { value ->
            homeItemsTest = value
        },
        genericError = { _, message ->
            error.postValue(message)
        },
        networkError = {
            error.postValue(TranslationManager.getString(TKeys.LOGIN_NETWORK_ERROR))
        }
    )
}

Because async was created a coroutine to run the code, and await will wait it and get result from it. But in your code, the coroutine in async is create anoher coroutine which await will not wait it