Seb
Seb

Reputation: 3215

Trigger a coroutine when the other is finished

I do have an app where I have to call an endpoint to pull a list of product and another endpoint to pull the list of product type.

The second endpoint only return the list of type but I need to add the type All and add the total number of products`

So it looks like: -> Endpoint call for list of products: return a json with all the products -> Endpoint call for list of types: return all types.

When all types are received I add the type all at the beginning of the list and count the number of products. So I need to have the first endpoint call done prior to calling the other one.

I did this way for the init:


   private var listOfProducts: List<Product> = emptyList()
   private var listOfTypes: List<Type> = emptyList()
   private var selectedType: String = ""

   init {
      fetchProducts()
      fetchTypes()
   }

and the fetch place is call:

   fun fetchProducts() {
      viewModelScope.launch {
         API_CALL {
            listOfProducts = response
         }
      }
   }
   
   fun fetchTypes() {
      viewModelScope.launch {
         API_CALL {
            nb_films = listOfProducts.count()
            listOfTypes = Type("all", nb_films) + response
         }
      }
   }

So the code is not perfect, but it's just to sum up the issue.

I need to use information from the fetchProducts into the fetchTypes. In some cases, it fails because of the latency to get the data.

Any idea how to block the trigger of the fetchTypes or either making the 2 calls but waiting one to be ended before processing the data

Upvotes: 0

Views: 59

Answers (2)

Jayesh Kumar
Jayesh Kumar

Reputation: 1

I feel like you can use the concept of async await . Here is what I tried to do

init {
    viewModelScope.launch {
        callA()
        callB()
    }
}

private suspend fun callA() {
    println("***CallA")
    coroutineScope {
        val deffered = async {
            api1 {
                listOfProducts = it
            }
            listOfProducts
        }
        deffered.await()
        println("***callA result ${listOfProducts.size}")
    }
}

private suspend fun callB() {
    println("***CallB")
    coroutineScope {
        val deffered = async {
            api2 {
                println("***size of product = ${listOfProducts.size}")
                listOfTypes = it
            }
            listOfTypes
        }
        deffered.await()
        println("***callB result ${listOfTypes.size}")
    }
}

suspend fun api1(callback: (List<Product>) -> Unit) {
    println("***api1")
    delay(200L)
    val mockedResponse = listOf(
        Product("a"),
        Product("b"),
        Product("c"),
        Product("d"),
    )
    callback(mockedResponse)
}

suspend fun api2(callback: (List<Type>) -> Unit) {
    println("***api2")
    delay(300L)
    val mockedResponse = listOf(
        Type("a", 1),
        Type("b", 1),
        Type("c", 1),
        Type("d", 1),
    )
    callback(mockedResponse)
}

This is the order of response,

  • ***CallA
  • ***api1
  • ***callA result 4
  • ***CallB
  • ***api2
  • ***size of product = 4
  • ***callB result 4

Upvotes: 0

Moe
Moe

Reputation: 1626

Your API calls run concurrently, causing fetchTypes() to sometimes use an empty listOfProducts.

One solution would be to use StateFlow to reactively chain the calls and guarantee order:

// ViewModel
private val _products = MutableStateFlow<List<Product>>(emptyList())
val products: StateFlow<List<Product>> = _products

private val _types = MutableStateFlow<List<Type>>(emptyList())
val types: StateFlow<List<Type>> = _types

init {
    viewModelScope.launch {
        // 1. Fetch products first
        _products.value = api.getProducts() 

        // 2. Fetch types AFTER products are loaded
        val typesResponse = api.getTypes() 
        val allType = Type("All", _products.value.size) // Use product count
        _types.value = listOf(allType) + typesResponse 
    }
}

This way getTypes() starts only after getProducts() succeeds.

Upvotes: 0

Related Questions