3

I'm trying to use Kotlin's coroutines to avoid callback hell, but it doesnt look like I can in this specific situation, I would like some thougths about it.

I have this SyncService class which calls series of different methods to send data to the server like the following: SyncService calls Sync Student, which calls Student Repository, which calls DataSource that makes a server request sending the data through Apollo's Graphql Client. The same pattern follows in each of my features:

SyncService -> Sync Feature -> Feature Repository -> DataSource

So every one of the method that I call has this signature:

fun save(onSuccess: ()-> Unit, onError:()->Unit) {
  //To Stuff here
}

The problem is: When I sync and successfully save the Student on server, I need to sync his enrollment, and if I successfully save the enrollment, I need to sync another object and so on. It all depends on each other and I need to do it sequentially, that's why I was using callbacks. But as you can imagine, the code result is not very friendly, and me and my team starting searching for alternatives to keep it better. And we ended up with this extension function:

suspend fun <T> ApolloCall<T>.execute() = suspendCoroutine<Response<T>> { cont ->
enqueue(object: ApolloCall.Callback<T>() {
    override fun onResponse(response: Response<T>) {
        cont.resume(response)
    }

    override fun onFailure(e: ApolloException) {
        cont.resumeWithException(e)
    }
})
}

But the function in DataSource still has a onSuccess() and onError() as callbacks that needs to be passed to whoever call it.

 fun saveStudents(
        students: List<StudentInput>,
        onSuccess: () -> Unit,
        onError: (errorMessage: String) -> Unit) {

    runBlocking {

        try {
            val response = GraphQLClient.apolloInstance

                    .mutate(CreateStudentsMutation
                            .builder()
                            .students(students)
                            .build())
                    .execute()

            if (!response.hasErrors())
                onSuccess()
            else
                onError("Response has errors!")

        } catch (e: ApolloException) {
            e.printStackTrace()
            onError("Server error occurred!")
        }
    }
}

The SyncService class code changed to be like:

 private fun runSync(onComplete: () -> Unit) = async(CommonPool) {

    val syncStudentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
        syncStudents()
    }

    val syncEnrollmentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
        syncEnrollments()
    }

    syncStudentProcess.await()
    syncEnrollmentProcess.await()

    onComplete()
}

It does execute it sequentially, but I need a way to stop every other coroutine if any got any errors. Error that might come only from Apollo's So I've been trying a lot to find a way to simplify this code, but didn't get any good result. I don't even know if this chaining of callbacks can be simplify at all. That's why I came here to see some thoughts on it. TLDR: I want a way to execute all of my functions sequentially, and still be able to stop all coroutines if any got an exception without a lot o chaining callbacks.

4

0 回答 0