Skip to content

Instantly share code, notes, and snippets.

@withoutclass
Created April 18, 2018 14:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save withoutclass/6781fc45ad847d4116d9d681bbe6b093 to your computer and use it in GitHub Desktop.
Save withoutclass/6781fc45ad847d4116d9d681bbe6b093 to your computer and use it in GitHub Desktop.
Modification of Retrofit Coroutine adapter for returning `Either` type, from the Arrow-kt library. This was modified from Jake Wharton's version of the CoroutineCallAdapterFactory.
/*
* Copyright (C) 2016 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.duracell.powercheck.service.api
import arrow.core.Either
import kotlinx.coroutines.experimental.CompletableDeferred
import kotlinx.coroutines.experimental.Deferred
import retrofit2.Call
import retrofit2.CallAdapter
import retrofit2.Callback
import retrofit2.HttpException
import retrofit2.Response
import retrofit2.Retrofit
import timber.log.Timber
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
/**
* A [CallAdapter.Factory] for use with Kotlin coroutines.
*
* Adding this class to [Retrofit] allows you to return [Deferred] from
* service methods.
*
* interface MyService {
* @GET("user/me")
* Deferred<User> getUser()
* }
*
* There are two configurations supported for the [Deferred] type parameter:
*
* * Direct body (e.g., `Deferred<User>`) returns the deserialized body for 2XX responses, throws
* [HttpException] errors for non-2XX responses, and throws [IOException][java.io.IOException] for
* network errors.
* * Response wrapped body (e.g., `Deferred<Response<User>>`) returns a [Response] object for all
* HTTP responses and throws [IOException][java.io.IOException] for network errors
*/
class CoroutineCallAdapterFactory private constructor() : CallAdapter.Factory() {
companion object {
@JvmStatic
@JvmName("create")
operator fun invoke() = CoroutineCallAdapterFactory()
}
override fun get(returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit): CallAdapter<*, *>? {
if (Deferred::class.java != getRawType(returnType)) {
return null
}
if (returnType !is ParameterizedType) {
throw IllegalStateException(
"Deferred return type must be parameterized as Deferred<Foo> or Deferred<out Foo>")
}
val responseType = getParameterUpperBound(0, returnType)
val rawDeferredType = getRawType(responseType)
return if (rawDeferredType == Response::class.java) {
if (responseType !is ParameterizedType) {
throw IllegalStateException(
"Response must be parameterized as Response<Foo> or Response<out Foo>")
}
ResponseCallAdapter<Any>(getParameterUpperBound(0, responseType))
} else if (rawDeferredType == Either::class.java) {
if (responseType !is ParameterizedType) {
throw IllegalStateException(
"Response must be parameterized as Either<Throwable, Foo>")
}
/**
* We're checking the inner type on our [Either] here. If it's a [Response], we want to use a different adapter.
*/
val param = getRawType(getParameterUpperBound(1, responseType))
Timber.d("Inner type is: $param")
val innerResponseType = getParameterUpperBound(1, responseType) as ParameterizedType
if (param == Response::class.java) {
EitherResponseCallAdapter<Any>(getParameterUpperBound(0, innerResponseType))
} else {
EitherBodyCallAdapter<Any>(getParameterUpperBound(1, responseType))
}
} else {
BodyCallAdapter<Any>(responseType)
}
}
private class BodyCallAdapter<T>(private val responseType: Type) : CallAdapter<T, Deferred<T>> {
override fun responseType() = responseType
override fun adapt(call: Call<T>): Deferred<T> {
val deferred = CompletableDeferred<T>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.completeExceptionally(t)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
deferred.complete(response.body()!!)
} else {
deferred.completeExceptionally(HttpException(response))
}
}
})
return deferred
}
}
private class ResponseCallAdapter<T>(private val responseType: Type) : CallAdapter<T, Deferred<Response<T>>> {
override fun responseType() = responseType
override fun adapt(call: Call<T>): Deferred<Response<T>> {
val deferred = CompletableDeferred<Response<T>>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.completeExceptionally(t)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
deferred.complete(response)
}
})
return deferred
}
}
/**
* This is an [Either] call adapter used for handling [Result] as the success type.
*/
private class EitherResponseCallAdapter<T>(private val responseType: Type) : CallAdapter<T, Deferred<Either<Throwable, Response<T>>>> {
override fun responseType() = responseType
override fun adapt(call: Call<T>): Deferred<Either<Throwable, Response<T>>> {
val deferred = CompletableDeferred<Either<Throwable, Response<T>>>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.complete(Either.left(t))
}
override fun onResponse(call: Call<T>, response: Response<T>) {
deferred.complete(Either.right(response))
}
})
return deferred
}
}
/**
* This is an [Either] call adapter user for getting a deserialized body of type T.
*/
private class EitherBodyCallAdapter<T>(private val responseType: Type) : CallAdapter<T, Deferred<Either<Throwable, T>>> {
override fun responseType() = responseType
override fun adapt(call: Call<T>): Deferred<Either<Throwable, T>> {
val deferred = CompletableDeferred<Either<Throwable, T>>()
deferred.invokeOnCompletion {
if (deferred.isCancelled) {
call.cancel()
}
}
call.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
deferred.complete(Either.left(t))
}
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
deferred.complete(Either.right(response.body()!!))
} else {
deferred.complete(Either.left(HttpException(response)))
}
}
})
return deferred
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment