Hi everyone, I have a scenario where I need to mak...
# coroutines
v
Hi everyone, I have a scenario where I need to make an API call, and based on its response, trigger another API call. What’s the recommended best practice for handling this flow using Kotlin coroutines?
j
If each API call is a suspend function, just call one after the other, nothing special is required
v
Okay is this the correct way?
Copy code
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

suspend fun apiCallOne(): Result<String> = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
    try {
        // Simulated network call
        Result.success("Token123")
    } catch (e: Exception) {
        Result.failure(e)
    }
}

suspend fun apiCallTwo(token: String): Result<String> = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
    try {
        // Simulated dependent network call
        Result.success("Data for token: $token")
    } catch (e: Exception) {
        Result.failure(e)
    }
}

suspend fun performChainedApiCalls() {
        apiCallOne().fold(
            onSuccess = { token ->
                apiCallTwo(token).fold(
                    onSuccess = { finalData ->
                        println("Success: $finalData")
                    },
                    onFailure = { e2 ->
                        println("Second API failed: ${e2.message}")
                    }
                )
            },
            onFailure = { e1 ->
                println("First API failed: ${e1.message}")
            }
        )
}
j
If you get rid of all the result wrapping, it becomes much simpler
v
Sorry I didn't get it. Can you please give me an example?
j
I meant this:
Copy code
suspend fun apiCallOne(): String {
    // Simulated network call
    delay(300.milliseconds)
    return "Token123"
}

suspend fun apiCallTwo(token: String): String {
    // Simulated dependent network call
    delay(300.milliseconds)
    return "Data for token: $token"
}

suspend fun performChainedApiCalls() {
    val token = apiCallOne()
    val finalData = apiCallTwo(token)
    println("Success: $finalData")
}
👍 2
☝🏼 1
If we're discussing coroutines, we don't have to deal with
Result
. This is an orthogonal concept. Also, I believe
Result
is harmful in this case, it complicates the code a lot. The error handling at a higher level would deal with it anyway. But as I said that's a separate discussion 🙂
g
to elaborate on @Joffrey's answer, the whole point of coroutines is that is that it lets you avoid callbacks with results. The magic of a coroutine is that it can convert a callback invocation into a return statement, so instead of having to nest callbacks inside callbacks, you can simply write much more normal looking functions --keeping in mind
suspend
semantics. And further, It is extremely poor practice to write
catch(e: Exception)
pretty much ever. This is almost certainly going to get you into trouble at some point. is there a reason you want to
catch(ex: Exception) { println("failed API call one") }
instead of simply `throw`ing
e
?
thank you color 2
v
thanks Joffrey, for sharing the code. I get your point.
thanks geoff for explaing me in detail. why
It is extremely poor practice to write catch(e: Exception) pretty much ever
? How to find errors if we get any from api?
Copy code
is there a reason you want to catch(ex: Exception) { println("failed API call one") } instead of simply throwing e?
How do we know if api one fails and what is the cause of error?
j
If you call both APIs in a row in regular non-resulty code, you'll still get an exception that will interrupt the flow if the first call fails. You can report the error at a higher level without bloating the code around each call. If you need to handle the specific failure by doing something else right here, then a try/catch can be appropriate, but then most likely you have something more specific than
Exception
. Surely you wouldn't handle
ClassCastException
the same as
IOException
v
Thanks Joffrey for explaining me in detail. I get your point now.