Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to run a suspend function inside another one without waiting for its result?

I have a scenario where my code has to send an api call and move on with its work (which contains another api call) without waiting for the result of the first call.

Right now I do this in my viewmodel

fun showItem(id:Int) {
   launch{
       repo.markItemRead(id)
   }
   launch {
       try {
           val item = repo.getItemById(id).getOrThrow
           commands.postValue(ShowItemCommand(item))
       } catch (t:Throwable) {
           commands.postValue(ShowError(R.string.error_retrieve_item))
           repo.logError(t)
       }
   }
}

this calls the repository which has these two functions

suspend fun markItemRead(id) {
    try {
        service.markItemAsRead(id)
    } catch(ignored:Throwable) {
    }
}

suspend fun getItemById(id) : Result<ItemData> {
    return try {
       val response : ItemEntity = service.getItemById(id)
       val item  = response.toData()
       Result.Success(item)
    } catch (t:Throwable) {
        Result.Failure(t)
    }
}

I would prefer it if the repository did all those jobs because one has to follow the other every time.

Unfortunatelly when I try to do something like this in my repository:

suspend fun getItemById(id:Int) : Result<ItemData> {
    try {
        service.markItemAsRead(id)
    } catch(ignored:Throwable) {
    }
    return try {
       val response : ItemEntity = service.getItemById(id)
       val item  = response.toData()
       Result.Success(item)
    } catch (t:Throwable) {
        Result.Failure(t)
    }
}

It waits for the markItemAsRead function to finish before moving on

Other than defining a scope for the repository and putting the markItemAsRead call inside a launch (which I have read is incorrect to do inside a suspending function) is there another way of doing this inside the repository?

like image 429
Cruces Avatar asked Nov 25 '22 22:11

Cruces


1 Answers

You can use coroutineScope or supervisorScope in the repository, depending on your needs. Both functions are designed for parallel decomposition of work. These functions return as soon as the given block and all its children coroutines are completed.

When any child coroutine in coroutineScope fails, this scope fails and all the rest of the children are cancelled. Unlike coroutineScope, a failure of a child coroutine in supervisorScope does not cause this scope to fail and does not affect its other children, so a custom policy for handling failures of its children can be implemented.

Please choose what best suits your needs. Example of usage:

suspend fun getItemByIdAndMarkRead(id: Int) : Result<ItemData> = supervisorScope {
    launch {
        try {
            service.markItemAsRead(id)
        } catch(ignored:Throwable) { }
    }

    return@supervisorScope withContext(Dispatchers.Default) {
        try {
            val response : ItemEntity = service.getItemById(id)
            val item  = response.toData()
            Result.Success(item)
        } catch (t: Throwable) {
            Result.Failure(t)
        }
    }
}

service.markItemAsRead(id) and service.getItemById(id) will execute in parallel.

like image 94
Sergey Avatar answered Nov 29 '22 04:11

Sergey