Search code examples
kotlinkotlin-coroutineskotlin-inline-class

Kotlin: inline function and shared type


I want to call multiple API calls at once using coroutines. So far I used coroutines only to call single API call which returned result in form off success or error. Its using inline function. But I need to understand how to use this inline function parameter for data class.

Class JsonResponse is open class which is result for every single API call. But if I use T inside my data class, its red. If I use there type JsonResponse, it is returning JsonResponse type, but not that exact type what apiBlock suppose to return.

Example: apiBlock can contain task, which will return LoginResponse. LoginResponse is extending JsonResponse. But if I use JsonResponse instead T inside my data class, I will have generic JsonResponse inside my onSuccess callback.

My goal here is to initialize multiple tasks, run them inside coroutine as async, then wait for all tasks to finish and if some of them will fail, return failed ones as array so they can be called again.

Here is code what I want to achieve:

data class ApiTask(
        val apiBlock: suspend CoroutineScope.() -> T,
        val onSuccess: suspend (T)->Unit)
protected fun<T: JsonResponse> apiCallChained(
        apiBlocks: List<ApiTask>,
        onError: ((List<ApiTask>)->Unit),
        onSuccess: ()->Unit){

        val failedTasks = mutableListOf<ApiTask>()
        apiBlocks.forEach { apiBlock->
            launch(Dispatchers.Main){
                val (r, err) = withContext(Dispatchers.IO){
                    try {
                        apiBlock.apiBlock(this) to null
                    } catch (e: ApiCallError) {
                        null to e
                    }
                }

                when {
                    r != null -> {
                        apiBlock.onSuccess(r)
                    }
                    err != null -> failedTasks.add(apiBlock)
                }
            }
        }

        if (failedTasks.isEmpty()) onSuccess.invoke() else onError.invoke(failedTasks)
 }

Here is my working example for single API call:

protected fun<T: JsonResponse> apiCall(apiBlock: suspend CoroutineScope.() -> T,
                                              onError: ((ApiCallError)->Unit)? = null,
                                              onDone: (()->Unit)? = null,
                                              onSuccess: suspend (T)->Unit): Job {

        return launch(Dispatchers.Main){
            val (r, err) = withContext(Dispatchers.IO){
                try {
                    apiBlock() to null
                } catch (e: ApiCallError) {
                    null to e
                }
            }
            onDone?.invoke()
            when {
                r != null -> onSuccess(r)
                err != null -> {
                    onError?.invoke(err)
                }
            }
        }
    }

Example of ApiBlock parameter value:

class ApiLogin(js: JSONObject): JsonResponse(js) {

    companion object {
        @Throws(ApiCallError::class)
        operator fun invoke(api: AppApi, email: String, pass: String): ApiLogin{
            return ApiLogin(api.apiLoginUser(email, pass))
        }
    }

    class LoginServerResponse(js: JSONObject): JsonResponse(js){
        val httpCode by JsInt("httpCode")
        val session by JsString("session")
    }

    val r = LoginServerResponse(js)

}

This class is then used as

apiBlock = { ApiLogin(app.api, email, pass) }

UPDATE: Fixed it like this, its working.

data class ApiTask<out T: JsonResponse>(
            val apiBlock: suspend CoroutineScope.() -> T,
            val onSuccess: suspend (@UnsafeVariance T)->Unit)

Solution

  • Your ApiTask also needs a generic parameter:

    data class ApiTask<T : JsonResponse>(
        val apiBlock: suspend CoroutineScope.() -> T,
        val onSuccess: suspend (T) -> Unit
    )
    

    Then you have to add this generic parameter in the apiCallChained function too:

    protected fun <T : JsonResponse> apiCallChained(
        apiBlocks: List<ApiTask<T>>,
        onError: ((List<ApiTask<T>>) -> Unit),
        onSuccess: () -> Unit
    ) {
        val failedTasks = mutableListOf<ApiTask<T>>()
        apiBlocks.forEach { apiBlock ->
            launch(Dispatchers.Main) {
                val (r, err) = withContext(Dispatchers.IO) {
                    try {
                        apiBlock.apiBlock(this) to null
                    } catch (e: ApiCallError) {
                        null to e
                    }
                }
    
                when {
                    r != null -> {
                        apiBlock.onSuccess(r)
                    }
    
                    err != null -> failedTasks.add(apiBlock)
                }
            }
        }
    
        if (failedTasks.isEmpty()) onSuccess.invoke() else onError.invoke(failedTasks)
    }