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 casecollect
action lambda will be invoked when data is emitted in either of flows. In yourViewModel
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 acceptsFlow
s to combine andtransform
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
Flow
s and invokestransform
withresult1
andresult2
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
}