Run two suspend functions in parallel and return when first one returns - android

I want to run to suspend functions in parallel and return result once the faster one of them returns result.
I tried doing the following, but I think it only returns when both of them have finished and it takes too much time, since I'm waiting for both.
val coroutineScope = CoroutineScope(Dispatchers.Main)
val a = coroutineScope.async {
a(context)
}
val b = coroutineScope.async {
b(context)
}
val results = listOf(a, b).awaitAll()
return if (results.any { it is RunSuccess }) {
...
} else {
...
}
Any ideas?

You can use select as follows :
suspend fun doWork(): String = coroutineScope {
select<String> {
async { work1() }.onAwait { it }
async { work2() }.onAwait { it }
}.also {
coroutineContext.cancelChildren()
}
}
On this example is returning a String but you can change it with whatever you want, it depends on what your work is returning.
In case you are looking for more functional programming version you can use raceN from Arrow
Where you have this method
public suspend inline fun <A, B> raceN(crossinline fa: suspend CoroutineScope.() -> A, crossinline fb: suspend CoroutineScope.() -> B): Either<A, B> =
raceN(Dispatchers.Default, fa, fb)
And then you call this raceN method
public suspend inline fun <A, B> raceN(
ctx: CoroutineContext = EmptyCoroutineContext,
crossinline fa: suspend CoroutineScope.() -> A,
crossinline fb: suspend CoroutineScope.() -> B
): Either<A, B> =
coroutineScope {
val a = async(ctx) { fa() }
val b = async(ctx) { fb() }
select<Either<A, B>> {
a.onAwait.invoke { Either.Left(it) }
b.onAwait.invoke { Either.Right(it) }
}.also {
when (it) {
is Either.Left -> b.cancelAndJoin()
is Either.Right -> a.cancelAndJoin()
}
}
}

Arrow fx has a nice method called raceN.
https://arrow-kt.io/docs/apidocs/arrow-fx-coroutines/arrow.fx.coroutines/race-n.html

Related

Creating a Kotlin DSL to accept processing pipeline and deliver an end result

I want to try to write a DSL, which can launch multiple methods piped one after the other to give an end result of a specific type, lets call it “Result”. The intermediate results of each of the calls can have different types, lets call them for example “Type 1", “Type 2” etc. I tried to accomplish something like this with following code.
class PipelineBuilder<I, R> {
val steps = mutableListOf<suspend (Any?) -> Any?>()
var initialFunction: (suspend () -> I)? = null
fun startWith(step: suspend () -> I) {
initialFunction = step
}
inline fun <reified T> then(noinline step: suspend (T) -> Any) {
steps.add { step }
}
suspend fun execute(): Any? {
if (steps.isEmpty()) {
throw Exception("Your pipeline is empty.")
}
var result: Any? = initialFunction?.invoke()
if (steps.size == 1) {
return result
}
for (index in 0 until steps.size) {
result = steps[index].invoke(result)
}
return result
}
suspend fun <T> executeConcurrent(vararg steps: suspend () -> T): List<T> {
return steps.map { step ->
coroutineScope {
async { step() }
}
}.awaitAll()
}
}
suspend fun <I, R> pipeline(block: suspend PipelineBuilder<I, R>.() -> Unit): Any? {
val builder = PipelineBuilder<I, R>()
builder.block()
return builder.execute()
}
suspend fun type1Method(input: Int): Type1 {
delay(1000)
return Type1()
}
suspend fun type2Method(input: Type1): Type2 {
delay(1000)
return Type2()
}
suspend fun type3Method(input: Type2): Int {
delay(1000)
return 3
}
class Type1
class Type2
class Type3
fun main() {
runBlocking {
val result = pipeline<Type1, Int> {
startWith { type1Method(1) }
then<Type1> {
type2Method(it)
}
then<Type2> {
type3Method(it)
}
}
println(result)
}
}
The pipeline should be able to execute multiple steps concurrently as well and combine the results in a combined result. When calling execute, you should execute the entire pipeline and then get an end result.
When I execute the code above, I don't get a "3" as a result, but "Function2<com.unisoft.myapplication.Type2, kotlin.coroutines.Continuation<? super java.lang.Object>, java.lang.Object>
". Where did I make a mistake and is there a simpler way of achieving this?

What is the substitute for runBlocking Coroutines in fragments and activities?

It is recommended to not use GlobalScope and runBlocking.
I have implemented changes in order to this topic:
End flow/coroutines task before go further null issue
However it doesn't work well as previously with runBlocking. In brief icon doesn't change, data is not on time.
My case is to change icon depending on the boolean.
usecase with Flow
class GetNotificationListItemDetailsUseCase #Inject constructor(private val notificationDao: NotificationDao): BaseFlowUseCase<Unit, List<NotificationItemsResponse.NotificationItemData>>() {
override fun create(params: Unit): Flow<List<NotificationItemsResponse.NotificationItemData>> {
return flow{
emit(notificationDao.readAllData())
}
}
}
viewmodel
val actualNotificationList: Flow<List<NotificationItemsResponse.NotificationItemData>> = getNotificationListItemDetailsUseCase.build(Unit)
fragment
private fun getActualNotificationList() : Boolean {
lifecycleScope.launch {
vm.actualNotificationList
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect { response ->
notificationData.value = response
val notificationDataString = notificationData.value.toString()
val stringToCheck = "isRead=false"
isNotificationNotRead = (notificationDataString.contains(stringToCheck))
}
}
return isNotificationNotRead
}
on method onViewCreated I have initToolbar to check if it's true and make action, with runBlokcing worked.
fun initToolbar{
if (onReceived) {
Log.d("onReceivedGoes", "GOES IF")
} else {
Log.d("onReceivedGoes", "GOES ELSE")
getActualNotificationList()
}
onReceived = false
val item = menu.findItem(R.id.action_notification_list)
when {
isNotificationNotRead && !isOutcomed -> {
item.setIcon(R.drawable.image_icon_change)
}
}
coroutine job before change, it worked well
val job = GlobalScope.launch {
vm.getNotificationListItemDetailsUseCase.build(Unit).collect {
notificationData.value = it
val notificationDataString = notificationData.value.toString()
val stringToCheck = "isRead=false"
isNotificationNotRead = (notificationDataString.contains(stringToCheck))
}
}
runBlocking {
job.join()
}
}
Another question is I have the same thing to do in MainActivity, but I do not use there a flow just suspend function.
UseCase
class UpdateNotificationListItemUseCase #Inject constructor(private val notificationDao: NotificationDao): BaseUpdateBooleanUseCase<Int, Boolean, Boolean, Boolean, Unit>() {
override suspend fun create(itemId: Int, isRead: Boolean, isArchived: Boolean, isAccepted: Boolean){
notificationDao.updateBooleans(itemId, isRead, isArchived, isAccepted)
}
}
MainActivity
val job = GlobalScope.launch { vm.getIdWithUpdate() }
runBlocking {
job.join()
}
MainViewmodel
suspend fun getIdWithUpdate() {
var id = ""
id = notificationAppSessionStorage.getString(
notificationAppSessionStorage.getIncomingKeyValueStorage(),
""
)
if (id != "") {
updateNotificationListItemUseCase.build(id.toInt(), true, false, false)
}
}
}
EDIT1:
collect in fragments works perfectly, thanks
What about MainActivity and using this usecase with suspend fun without flow.
I have read documentation https://developer.android.com/kotlin/coroutines/coroutines-best-practices
val IODispatcher: CoroutineDispatcher = Dispatchers.IO
val externalScope: CoroutineScope = CoroutineScope(IODispatcher)
suspend {
externalScope.launch(IODispatcher) {
vm.getIdWithUpdate()
}.join()
}
Second option, but here I do not wait until job is done
suspend {
withContext(Dispatchers.IO) {
vm.getIdWithUpdate()
}
}
What do you think about it?
You can try to update the icon in the collect block:
private fun getActualNotificationList() = lifecycleScope.launch {
vm.actualNotificationList
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect { response ->
notificationData.value = response
val notificationDataString = notificationData.value.toString()
val stringToCheck = "isRead=false"
val isNotificationNotRead = (notificationDataString.contains(stringToCheck))
val item = menu.findItem(R.id.action_notification_list)
when {
isNotificationNotRead && !isOutcomed -> {
item.setIcon(R.drawable.image_icon_change)
}
}
}
}
Using runBlocking you are blocking the Main Thread, which may cause an ANR.

Kotlin-flow: Suspension functions can be called only within coroutine body

I'm trying to understand how Kotlin Flow works with coroutines and I decided to work on a test app. I'm using an MVVM + clean architecture. Here's a snippet of my data layer
class QuestionListRepositoryImpl(
private val localDataStore: LocalDataStore,
private val remoteDataStore: RemoteDataStore,
private val mapper: DataToDomainMapper,
private val coroutineScopes: CoroutineScopes) : GetQuestionRepository {
override suspend fun getQuestions(): Either<Failure, Flow<List<QuestionDomainModel>>> {
coroutineScopes.io.launch {
remoteDataStore.fetchQuestions().map {
localDataStore.deleteAllQuestion(). // Error is here
localDataStore.saveQuestions(it.items) // and here
}
}
val concatMapper = flow {
emitAll(localDataStore.readQuestions().map { allDataModel ->
mapper.map(allDataModel)
})
}
return Either.Right(concatMapper.distinctUntilChanged())
}
}
Here's my dataStore interface:
interface LocalDataStore {
suspend fun saveQuestions(data: List<QuestionDataModel>)
suspend fun readQuestions(): Flow<List<QuestionDataModel>>
suspend fun deleteAllQuestion()
}
interface RemoteDataStore {
suspend fun fetchQuestions(): Either<Failure, QuestionListApiResponse>
}
I'm having an issue in the repository in the lines where deleteAllQuestion() and saveQuestions() are called. Putting the cursor on any of the 2 function calls shows me this error: Suspension functions can be called only within coroutine body
Can anyone point me to the right direction ? What am I missing here ?
EDIT The implementation of Either.map taken from the comments:
fun <T, L, R> Either<L, R>.map(fn: (R) -> (T)): Either<L, T> =
this.flatMap(fn.c(::right))
fun <T, L, R> Either<L, R>.flatMap(fn: (R) -> Either<L, T>): Either<L, T> =
when (this) {
is Either.Left -> Either.Left(failure)
is Either.Right -> fn(success)
}

NetworkBoundResource with Kotlin coroutines

Do you have any ideas how to implement repository pattern with NetworkBoundResource and Kotlin coroutines? I know we can launch a coroutine withing a GlobalScope, but it may lead to coroutine leak. I would like to pass a viewModelScope as a parameter, but it is a bit tricky, when it comes to implementation (because my repository doesn't know a CoroutineScope of any ViewModel).
abstract class NetworkBoundResource<ResultType, RequestType>
#MainThread constructor(
private val coroutineScope: CoroutineScope
) {
private val result = MediatorLiveData<Resource<ResultType>>()
init {
result.value = Resource.loading(null)
#Suppress("LeakingThis")
val dbSource = loadFromDb()
result.addSource(dbSource) { data ->
result.removeSource(dbSource)
if (shouldFetch(data)) {
fetchFromNetwork(dbSource)
} else {
result.addSource(dbSource) { newData ->
setValue(Resource.success(newData))
}
}
}
}
#MainThread
private fun setValue(newValue: Resource<ResultType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
val apiResponse = createCall()
result.addSource(dbSource) { newData ->
setValue(Resource.loading(newData))
}
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
result.removeSource(dbSource)
when (response) {
is ApiSuccessResponse -> {
coroutineScope.launch(Dispatchers.IO) {
saveCallResult(processResponse(response))
withContext(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
}
is ApiEmptyResponse -> {
coroutineScope.launch(Dispatchers.Main) {
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
is ApiErrorResponse -> {
onFetchFailed()
result.addSource(dbSource) { newData ->
setValue(Resource.error(response.errorMessage, newData))
}
}
}
}
}
}
Update (2020-05-27):
A way which is more idiomatic to the Kotlin language than my previous examples, uses the Flow APIs, and borrows from Juan's answer can be represented as a standalone function like the following:
inline fun <ResultType, RequestType> networkBoundResource(
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend () -> RequestType,
crossinline saveFetchResult: suspend (RequestType) -> Unit,
crossinline onFetchFailed: (Throwable) -> Unit = { Unit },
crossinline shouldFetch: (ResultType) -> Boolean = { true }
) = flow<Resource<ResultType>> {
emit(Resource.Loading(null))
val data = query().first()
val flow = if (shouldFetch(data)) {
emit(Resource.Loading(data))
try {
saveFetchResult(fetch())
query().map { Resource.Success(it) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable, it) }
}
} else {
query().map { Resource.Success(it) }
}
emitAll(flow)
}
The above code can be called from a class, e.g. a Repository, like so:
fun getItems(request: MyRequest): Flow<Resource<List<MyItem>>> {
return networkBoundResource(
query = { dao.queryAll() },
fetch = { retrofitService.getItems(request) },
saveFetchResult = { items -> dao.insert(items) }
)
}
Original answer:
This is how I've been doing it using the livedata-ktx artifact; no need to pass in any CoroutineScope. The class also uses just one type instead of two (e.g. ResultType/RequestType) since I always end up using an adapter elsewhere for mapping those.
import androidx.lifecycle.LiveData
import androidx.lifecycle.liveData
import androidx.lifecycle.map
import nihk.core.Resource
// Adapted from: https://developer.android.com/topic/libraries/architecture/coroutines
abstract class NetworkBoundResource<T> {
fun asLiveData() = liveData<Resource<T>> {
emit(Resource.Loading(null))
if (shouldFetch(query())) {
val disposable = emitSource(queryObservable().map { Resource.Loading(it) })
try {
val fetchedData = fetch()
// Stop the previous emission to avoid dispatching the saveCallResult as `Resource.Loading`.
disposable.dispose()
saveFetchResult(fetchedData)
// Re-establish the emission as `Resource.Success`.
emitSource(queryObservable().map { Resource.Success(it) })
} catch (e: Exception) {
onFetchFailed(e)
emitSource(queryObservable().map { Resource.Error(e, it) })
}
} else {
emitSource(queryObservable().map { Resource.Success(it) })
}
}
abstract suspend fun query(): T
abstract fun queryObservable(): LiveData<T>
abstract suspend fun fetch(): T
abstract suspend fun saveFetchResult(data: T)
open fun onFetchFailed(exception: Exception) = Unit
open fun shouldFetch(data: T) = true
}
Like #CommonsWare said in the comments, however, it'd be nicer to just expose a Flow<T>. Here's what I've tried coming up with to do that. Note that I haven't used this code in production, so buyer beware.
import kotlinx.coroutines.flow.*
import nihk.core.Resource
abstract class NetworkBoundResource<T> {
fun asFlow(): Flow<Resource<T>> = flow {
val flow = query()
.onStart { emit(Resource.Loading<T>(null)) }
.flatMapConcat { data ->
if (shouldFetch(data)) {
emit(Resource.Loading(data))
try {
saveFetchResult(fetch())
query().map { Resource.Success(it) }
} catch (throwable: Throwable) {
onFetchFailed(throwable)
query().map { Resource.Error(throwable, it) }
}
} else {
query().map { Resource.Success(it) }
}
}
emitAll(flow)
}
abstract fun query(): Flow<T>
abstract suspend fun fetch(): T
abstract suspend fun saveFetchResult(data: T)
open fun onFetchFailed(throwable: Throwable) = Unit
open fun shouldFetch(data: T) = true
}
#N1hk answer works right, this is just a different implementation that doesn't use the flatMapConcat operator (it is marked as FlowPreview at this moment)
#FlowPreview
#ExperimentalCoroutinesApi
abstract class NetworkBoundResource<ResultType, RequestType> {
fun asFlow() = flow {
emit(Resource.loading(null))
val dbValue = loadFromDb().first()
if (shouldFetch(dbValue)) {
emit(Resource.loading(dbValue))
when (val apiResponse = fetchFromNetwork()) {
is ApiSuccessResponse -> {
saveNetworkResult(processResponse(apiResponse))
emitAll(loadFromDb().map { Resource.success(it) })
}
is ApiErrorResponse -> {
onFetchFailed()
emitAll(loadFromDb().map { Resource.error(apiResponse.errorMessage, it) })
}
}
} else {
emitAll(loadFromDb().map { Resource.success(it) })
}
}
protected open fun onFetchFailed() {
// Implement in sub-classes to handle errors
}
#WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
#WorkerThread
protected abstract suspend fun saveNetworkResult(item: RequestType)
#MainThread
protected abstract fun shouldFetch(data: ResultType?): Boolean
#MainThread
protected abstract fun loadFromDb(): Flow<ResultType>
#MainThread
protected abstract suspend fun fetchFromNetwork(): ApiResponse<RequestType>
}
I am new to Kotlin Coroutine. I just come across this problem this week.
I think if you go with the repository pattern as mentioned in the post above, my opinion is feeling free to pass a CoroutineScope into the NetworkBoundResource. The CoroutineScope can be one of the parameters of the function in the Repository, which returns a LiveData, like:
suspend fun getData(scope: CoroutineScope): LiveDate<T>
Pass the build-in scope viewmodelscope as the CoroutineScope when calling getData() in your ViewModel, so NetworkBoundResource will work within the viewmodelscope and be bound with the Viewmodel's lifecycle. The coroutine in the NetworkBoundResource will be cancelled when ViewModel is dead, which would be a benefit.
To use the build-in scope viewmodelscope, don't forget add below in your build.gradle.
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha01'

How to achieve concurrency with Kotlin coroutines?

I have list of carousels and run over each carousel and based on the carousel query I do fetchAssets() and fetchAssets() is Kotlin suspended function but the problem is each function is called when the previous one is finished I want to achieve concurency?
uiScope.launch {
carousels.mapIndexed { index, carousel ->
when (val assetsResult = assetRepository.fetchAssets(carousel.query)) {
is Response.Success<List<Asset>> -> {
if (assetsResult.data.isNotEmpty()) {
val contentRow = ContentRow(assetsResult.data)
contentRows.add(contentRow)
contentRowsmutableData.postValue(contentRows)
}
}
is Response.Failure -> {
}
}
}
}
override suspend fun fetchAssets(query: String): Response<List<Asset>> {
return suspendCoroutine { cont ->doHttp(assetsEndpoint, JsonHttpCall("GET"),
object : JsonReaderResponseHandler() {
override fun onSuccess(jsonReader: JsonReader) {
val apiAsset = ApiAssetList(jsonReader)
cont.resume(Response.Success(apiAsset.items))
}
override fun onError(error: Error) {
cont.resume(Response.Failure("errorMessage"))
}
})
}
}```
You have to wrap your suspend function in an async block, then wait for all async operations to complete:
uiScope.launch {
val asyncList = carousels.map { carousel ->
async { assetRepository.fetchAssets(carousel.query) }
}
val results = asyncList.awaitAll()
results.forEach { result ->
when (result) {
is Response.Success -> TODO()
is Response.Failure -> TODO()
}
}
}
suspend fun fetchAssets(query: String): Response<List<Asset>>
Edit: if you want to update the UI as each completes, you need to change it like this:
carousels.forEach { carousel ->
uiScope.launch {
val result = fetchAssets(carousel.query)
when (result) {
is Response.Success -> {
if (result.data.isNotEmpty()) {
val contentRow = ContentRow(result.data)
contentRows.add(contentRow)
contentRowsmutableData.postValue(contentRows)
}
}
is Response.Failure -> TODO()
}
}
}
Check this for concurrency with Coroutines.

Categories

Resources