This is a small library that provides the Kotlin Coroutines suspending extension Call.await()
for Retrofit 2
Based on kotlinx.coroutines implementation
Download the JAR:
Gradle:
compile 'ru.gildor.coroutines:kotlin-coroutines-retrofit:0.11.0'
Maven:
<dependency>
<groupId>ru.gildor.coroutines</groupId>
<artifactId>kotlin-coroutines-retrofit</artifactId>
<version>0.11.0</version>
</dependency>
NOTE: All examples in this README use
runBlocking
to build coroutine but it is only useful for testing or examples.For a real application you probably want to use some other coroutines builder that does not block a thread, for example
launch
from kotlinx.coroutines.If you want to use this library for UI please also check the Guide to UI programming with coroutines
There are three suspending extensions:
Common await API that returns a result or throws an exception
fun Call<T>.await(): T
In case of an HTTP error or an invocation exception await()
throws an exception
// You can use retrofit suspended extension inside any coroutine block
fun main(args: Array<String>) = runBlocking {
try {
// Wait (suspend) for result
val user: User = api.getUser("username").await()
// Now we can work with result object
println("User ${user.name} loaded")
} catch (e: HttpException) {
// Catch http errors
println("exception${e.code()}", e)
} catch (e: Throwable) {
// All other exceptions (non-http)
println("Something broken", e)
}
}
Common await API that returns a Response or throws an exception
fun Call<T>.awaitResponse(): Response<T>
In case of an invocation exception awaitResponse()
throws an exception
// You can use retrofit suspended extension inside any coroutine block
fun main(args: Array<String>) = runBlocking {
try {
// Wait (suspend) for response
val response: Response<User> = api.getUser("username").awaitResponse()
if (response.isSuccessful()) {
// Now we can work with response object
println("User ${response.body().name} loaded")
}
} catch (e: Throwable) {
// All other exceptions (non-http)
println("Something broken", e)
}
}
API based on sealed class Result:
fun Call<T>.awaitResult(): Result<T>
fun main(args: Array<String>) = runBlocking {
// Wait (suspend) for Result
val result: Result<User> = api.getUser("username").awaitResult()
// Check result type
when (result) {
//Successful HTTP result
is Result.Ok -> saveToDb(result.value)
// Any HTTP error
is Result.Error -> log("HTTP error with code ${result.error.code()}", result.error)
// Exception while request invocation
is Result.Exception -> log("Something broken", e)
}
}
Also, Result
has a few handy extension functions that allow to avoid when
block matching:
fun main(args: Array<String>) = runBlocking {
val result: User = api.getUser("username").awaitResult()
//Return value for success or null for any http error or exception
result.getOrNull()
//Return result or default value
result.getOrDefault(User("empty-user"))
//Return value or throw exception (HttpException or original exception)
result.getOrThrow()
//Also supports custom exceptions to override original ones
result.getOrThrow(IlleagalStateException("User request failed"))
}
All Result
classes also implemented one or both interfaces: ResponseResult
and ErrorResult
You can use them for access to shared properties of different classes from Result
fun main(args: Array<String>) = runBlocking {
val result: User = api.getUser("username").awaitResult()
//Result.Ok and Result.Error both implement ResponseResult
if (result is ResponseResult) {
//And after smart cast you now have an access to okhttp3 Response property of result
println("Result ${result.response.code()}: ${result.response.message()}")
}
//Result.Error and Result.Exception implement ErrorResult
if (result is ErrorResult) {
// Here yoy have an access to `exception` property of result
throw result.exception
}
}
To prevent unexpected behavior with a nullable body of response Call<Body?>
extensions .await()
and .awaitResult()
are available only for
non-nullable Call<Body>
or platform Call<Body!>
body types:
fun main(args: Array<String>) = runBlocking {
val user: Call<User> = api.getUser("username")
val userOrNull: Call<User?> = api.getUserOrNull("username")
// Doesn't work, because User is nullable
// userOrNull.await()
// Works for non-nullable type
try {
val result: User = user.await()
} catch (e: NullPointerException) {
// If body will be null you will get NullPointerException
}
// You can use .awaitResult() to catch possible problems with nullable body
val nullableResult = api.getUser("username").awaitResult().getOrNull()
// But type of body should be non-nullable
// api.getUserOrNull("username").awaitResult()
// If you still want to use nullable body to clarify your api
// use awaitResponse() instead:
val responseBody: User? = userOrNull.awaitResponse().body()
}
Sometimes you want to run a few requests in parallel and don't want to wait for the previous request to make the next one.
You can do that by wrapping calls with kotlinx.coroutines
async()
fun main(args: Array<String>) = runBlocking {
val users = listOf("user1", "user2", "user3")
.map { username ->
// Pass any coroutine context that fits better for your case
// Coroutine Dispatcher also controls parallelism level
// for CommonPool parallelism is `availableProcessors - 1`
// But you can use any custom dispatcher with any parallelism strategy
async(CommonPool) {
// Send request. We use `awaitResult()` here to avoid try/catch,
// but you can use `await()` and catch exceptions
api.getUser(username).awaitResult()
}
}
// Handle results
// in this example we get result or null in case of error and filter all nulls
.mapNotNull {
// Wait (suspend) for result of `async()` and get result of request
// We must call first `await()` only when all `async` blocks are created for parallel requests
it.await().getOrNull()
}
}
You can read more about concurrent usage of async in the kotlinx.coroutines guide