Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/gildor/kotlin-coroutines-retrofit

Kotlin Coroutines await() extension for Retrofit Call
https://github.com/gildor/kotlin-coroutines-retrofit

async coroutines kotlin kotlin-coroutines retrofit retrofit2

Last synced: 16 days ago
JSON representation

Kotlin Coroutines await() extension for Retrofit Call

Lists

README

        

# Kotlin Coroutines for Retrofit
[![CircleCI](https://circleci.com/gh/gildor/kotlin-coroutines-retrofit.svg?style=shield)](https://circleci.com/gh/gildor/kotlin-coroutines-retrofit)
[![codecov](https://codecov.io/gh/gildor/kotlin-coroutines-retrofit/branch/master/graph/badge.svg)](https://codecov.io/gh/gildor/kotlin-coroutines-retrofit)
[![codebeat badge](https://codebeat.co/badges/ccb15073-b84c-4f7d-a0fc-1bdba2b0e435)](https://codebeat.co/projects/github-com-gildor-kotlin-coroutines-retrofit-master)

This is a small library that provides the [Kotlin Coroutines](https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md) [suspending](https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md#suspending-functions) extension `Call.await()` for [Retrofit 2](https://github.com/square/retrofit)

Based on [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) implementation.

New version of library (after 1.0.0) support only Kotlin 1.3

Kotlin 1.2 and experimental coroutines are not supported anymore, but you can use version `0.13.0` for old projects.

## Download

Download the [JAR](https://bintray.com/gildor/maven/kotlin-coroutines-retrofit#files/ru/gildor/coroutines/kotlin-coroutines-retrofit):

Gradle:

```groovy
implementation 'ru.gildor.coroutines:kotlin-coroutines-retrofit:1.1.0'
```

Maven:

```xml

ru.gildor.coroutines
kotlin-coroutines-retrofit
1.1.0

```

## How to use

> NOTE: All examples in this README use [`runBlocking`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run-blocking.html) 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`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/launch.html) from [kotlinx.coroutines](https://github.com/kotlin/kotlinx.coroutines).
>
> If you want to use this library for UI please also check the [Guide to UI programming with coroutines](https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-guide-ui.md)

There are three suspending extensions:

### `.await()`

Common await API that returns a result or throws an exception
```kotlin
fun Call.await(): T
```

In case of an HTTP error or an invocation exception `await()` throws an exception

```kotlin
// You can use retrofit suspended extension inside any coroutine block
fun main(args: Array): Unit = 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)
}
}
```

### `.awaitResponse()`

Common await API that returns a [Response](https://square.github.io/retrofit/2.x/retrofit/retrofit2/Response.html) or throws an exception
```kotlin
fun Call.awaitResponse(): Response
```

In case of an invocation exception `awaitResponse()` throws an exception

```kotlin
// You can use retrofit suspended extension inside any coroutine block
fun main(args: Array): Unit = runBlocking {
try {
// Wait (suspend) for response
val response: Response = 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)
}
}
```

### `.awaitResult()`

API based on sealed class [Result](src/main/kotlin/ru/gildor/coroutines/retrofit/Result.kt):

```kotlin
fun Call.awaitResult(): Result
```

```kotlin
fun main(args: Array): Unit = runBlocking {
// Wait (suspend) for Result
val result: Result = 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:

```kotlin
fun main(args: Array): Unit = 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`

```kotlin
fun main(args: Array): Unit = 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
}
}
```

## Nullable body

To prevent unexpected behavior with a nullable body of response `Call`
extensions `.await()` and `.awaitResult()` are available only for
non-nullable `Call` or platform `Call` body types:

```kotlin
fun main(args: Array): Unit = runBlocking {
val user: Call = api.getUser("username")
val userOrNull: Call = 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()
}
```

## Parallel requests

By wrapping call with `kotlinx.coroutines` [async()](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/async.html), you may run a few requests parallelly without waiting for the previous request.

```kotlin
fun main(args: Array): Unit = 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](https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md#concurrent-using-async)