Home > other >  how to have wait for last task and results for each in coroutine on Android?
how to have wait for last task and results for each in coroutine on Android?

Time:12-21

I have two tasks. Both load data to view model (eg. LoadDataList1UseCase and LoadDataList2UseCase). When new fragment is started then data should be loaded in view model. But when any of load process finish then data fetched by it should be loaded to view (recycler view) but only when both finish then progress bar should be hidden.

I figure out some like below but doesn't work. What I miss? How look like correct approach?

class LoadDataList1UseCase {
   operator fun invoke() = flow { 
      delay(3_000)
      emit("a")
   }
}
class LoadDataList2UseCase {
   operator fun invoke() = flow { emit("b")}
}

//------------ method in view model:
suspend fun loadData() = withContext(Dispatchers.IO) {
   loadDataList1
      .onEatch { /*update screan*/}
   loadDataList2
      .onEatch { /*update screan*/}
}

and run it in runBlocking

I'm totally newbie in coroutine. In rx, I would be try mix combinedLatest and doOnComplite

CodePudding user response:

You do it in a similar way to RxJava, it is even named combine() as well:

loadDataList1
    .combine(loadDataList2, ::Pair)
    .collect { (first, second) ->
        /*update screen*/
    }

CodePudding user response:

  • You can use merge function to merge flows concurrently. In this case collect action lambda will be invoked when data is emitted in either of flows. In your ViewModel class:

    class MyViewModel(...) : ViewModel() {
    
        fun loadData() = viewModelScope.launch {
          merge(loadDataList1(), loadDataList2())
              .collect {
                  // update UI (RecyclerView)
              }
          // both flows are finished, hide progress bar here
        }
    }
    
  • There is also combine function (not extension function) that accepts Flows to combine and transform block, it defined like the following:

    fun <T1, T2, R> combine(flow: Flow, flow2: Flow, transform: suspend (T1, T2) -> R): Flow

    You can use it in your ViewModel class:

    class MyViewModel(...) : ViewModel() {
      init {
          combine(
              loadDataList1(),
              loadDataList2()
          ) { result1, result2 ->
              // use result1 and result2
              // hide progress
          }.launchIn(viewModelScope) // Terminal flow operator that launches the collection of the given flow in the scope. It is a shorthand for scope.launch { flow.collect() }.
      }
    }
    

    The above approach combines Flows and invokes transform with result1 and result2 params only when both are available.

CodePudding user response:

You can try some thing like this:

suspend fun saveInDb() {
    val value = GlobalScope.async {
       delay(1000)
       println("thread running on [${Thread.currentThread().name}]")
       10
    }
    println("value =  ${value.await()} thread running on [${Thread.currentThread().name}]")
} 

await will wait for the coroutine to finish and then run code below it

fun onClick(view: View) {
    res = ""
    button.isEnabled = false
    GlobalScope.launch(Dispatchers.Main){ // launches coroutine in main thread
         updateUi()
    }
}

suspend fun updateUi(){
    val value = GlobalScope.async { // creates worker thread
       res = withContext(Dispatchers.Default) {
          test()
       }
    }
    println(value.await()) //waits for workerthread to finish
    button.isEnabled = true //runs on ui thread as calling function is on Dispatchers.main
}
  • Related