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()
}
8
  • Is restService.getHomepage() or .getResult() suspend? If they are not getHomepageItems function will return immediately, which you probably don't want here. Commented Dec 15, 2021 at 12:34
  • @ArpitShukla yes they are both suspend functions Commented Dec 16, 2021 at 8: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? Commented Dec 16, 2021 at 9: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. Commented Dec 16, 2021 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. Commented Dec 16, 2021 at 12:18

2 Answers 2

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)
      }
  }
Sign up to request clarification or add additional context in comments.

3 Comments

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
What is invokeOnCompletion?
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
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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.