'how to implement Coroutine async/await on BaseViewModel

I'm using Retrofit with Coroutine using this structure to hit the API requests in my app but at some screens I send multiple request and I want to only show data if the all requests was loaded but I can't do that using this structure?

  interface MyApi{
        @GET(API_DATA)
        suspend fun getDataA(): Response<BaseResponse<MyApiResponse>>
    
        @GET(API_DATA)
        suspend fun getDataB(): Response<BaseResponse<MyApiResponse>>

       @GET(API_DATA)
        suspend fun getDataC(): Response<BaseResponse<MyApiResponse>>
    }

//

 class MyApiCalls(private val myApi: MyApi) {
     suspend fun getDataA() =
            myApi.getDataA()
    
     suspend fun getDataB() =
            myApi.getDataB()

     suspend fun getDataC() =
            myApi.getDataC()
    }

//

class MyRepository(
    private val myApiCalls: MyApiCalls
    ) : BaseRepository()  {
        suspend fun getDataA() = myApiCalls.getDataA()
        suspend fun getDataB() = myApiCalls.getDataB()
        suspend fun getDataC() = myApiCalls.getDataC()
}

//

class MyViewModel(
    private val repository: MyRepository,
) : BaseViewModel(repository) {
    val dataAStatus = SingleLiveEvent<Status>()
    val dataBStatus = SingleLiveEvent<Status>()
    val dataCStatus = SingleLiveEvent<Status>()
    fun getDataA() {
        performNetworkCall({
            repository.getDataA()
        }, dataAStatus )

    }

    fun getDataB() {
        performNetworkCall({
            repository.getDataB()
        }, dataBStatus)

    }

  fun getDataC() {
        performNetworkCall({
            repository.getDataC()
        }, dataCStatus)

    }

}

//

abstract class BaseViewModel(private val repository: BaseRepository) : ViewModel() {
    val showNetworkError = SingleLiveEvent<Boolean>()
    val statusToken = SingleLiveEvent<Status>()
    val logoutStatus = SingleLiveEvent<Status>()
    val refreshTokenStatus = SingleLiveEvent<Status>()

    fun <D> performNetworkCall(
        apiCall: suspend () -> Response<D>,
        status: SingleLiveEvent<Status>,
        doOnSuccess: (responseData: D?) -> Unit = {},
        doOnFailure: (() -> Any) = {}
    ) {
        if (isNetworkConnected()) {
            viewModelScope.launch {
                withContext(Dispatchers.IO) {
                    try {
                        status.postValue(Status.Loading)
                        val response = apiCall.invoke()
                        when {
                     
                            response.code() in 200..300 -> {
                                doOnSuccess(response.body())
                                status.postValue(Status.Success(response.body()))
                            }
                     
                            else -> {
                                doOnFailure()
                                status.postValue(Status.Error(
                    errorCode = ERRORS.DEFAULT_ERROR,
                    message = repository.getString(R.string.default_error)
                )
            )
                
                    } catch (e: Exception) {
                        doOnFailure()
                       status.postValue(
                Status.Error(
                    errorCode = ERRORS.DEFAULT_ERROR,
                    message = repository.getString(R.string.default_error)
                )
            )
                    }
                }
            }
        } else
            status.postValue(
                Status.Error(
                    errorCode = ERRORS.NO_INTRERNET,
                    message = repository.getString(R.string.no_internet_connection)
                )
            )
    }
}

I want to use aysnc await to read data from getDataA and getDataB on my activity/Fragment how to implement that using this structure and also keep the ability to use single request in other api requests



Solution 1:[1]

To call functions in parallel you can use async builder:

fun performRequests() {
    viewModelScope.launch {
        val request1 = async { repository.getDataA() }
        val request2 = async { repository.getDataB() }
        val request3 = async { repository.getDataC() }
         // all three requests run in parallel
        
        val response1 = request1.await()
        val response2 = request2.await()
        val response3 = request3.await()

        //... use those responses to get data, notify UI
    }
} 

In your current structure, I don't think there is a need in calling withContext(Dispatchers.IO) in performNetworkCall() method, unless you want the callbacks doOnSuccess and doOnFailure to be called in background thread.


UPDATE

Try to use next calls to reuse your current structure:

performNetworkCall({performRequests()}, status)

suspend fun performRequests(): Response<...> = coroutineScope {
    val request1 = async { repository.getDataA() }
    val request2 = async { repository.getDataB() }
    val request3 = async { repository.getDataC() }
    // all three requests run in parallel

    val response1 = request1.await()
    val response2 = request2.await()
    val response3 = request3.await()

    //... use those responses to compose a general Response object
    val generalResponse: Response<...> = "..."

    generalResponse
}

I used coroutineScope function, it is designed for parallel decomposition of work. When any child coroutine in this scope fails, this scope fails and all the rest of the children are cancelled.

Solution 2:[2]

Instead of dataAStatus and dataBStatus. You can have one single live data like SingleLiveEvent<Pair<Status,Status>>().

 val dataStatus = SingleLiveEvent<Pair<Status,Status>()
    private fun getAllData(){
        viewModelScope.launch {
            val dataFromA = myApiCalls.getDataA()
            val dataFromB = myApiCalls.getDataB()
            dataStatus.postValue(Pair(dataFromA,dataFromB))
            
        }
    }

Here myApiCalls.getDataA() and myApiCalls.getDataB() should be returning status.

And then observe on this live data (dataStatus) in your activity or fragment.

private fun observeData() {
        viewModel.dataStatus.observe(this){
            val firstData = it.first
            val secondData = it.second
        }
    }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Gaurav Rajput