Skip to content

Instantly share code, notes, and snippets.

@digitalbuddha
Created February 25, 2019 02:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save digitalbuddha/61e06b1b97ccf284ce796369f0632da2 to your computer and use it in GitHub Desktop.
Save digitalbuddha/61e06b1b97ccf284ce796369f0632da2 to your computer and use it in GitHub Desktop.
package com.jakewharton.retrofit2.adapter.kotlin.coroutines
import com.nytimes.android.external.store3.base.DiskRead
import com.nytimes.android.external.store3.base.DiskWrite
import com.nytimes.android.external.store3.base.Fetcher
import com.nytimes.android.external.store3.base.impl.RealStoreBuilder
import com.nytimes.android.external.store3.base.impl.Store
import com.nytimes.android.external.store3.base.impl.StoreBuilder
import com.nytimes.android.sample.data.remote.Persister
import com.nytimes.android.sample.data.remote.RefershOnStale
import kotlinx.coroutines.suspendCancellableCoroutine
import retrofit2.*
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
class StoreCallAdapterFactory private constructor() : CallAdapter.Factory() {
val stores = mutableMapOf<Type, StoreCallAdapter<Any>>()
companion object {
@JvmStatic
@JvmName("create")
operator fun invoke() = StoreCallAdapterFactory()
}
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (Store::class.java != getRawType(returnType)) {
return null
}
if (returnType !is ParameterizedType) {
throw IllegalStateException(
"Store return type must be parameterized as Store<Foo> or Store<out Foo>")
}
val responseType = getParameterUpperBound(0, returnType)
return stores.getOrPut(responseType) { StoreCallAdapter(responseType, annotations) }
}
class StoreCallAdapter<T>(
private val responseType: Type,
private val annotations: Array<out Annotation>
) : CallAdapter<T, Store<T, Call<T>>> {
var store: Store<T, Call<T>>? = null
@Synchronized
override fun adapt(call: Call<T>): Store<T, Call<T>> {
if (store == null) {
val builder = StoreBuilder.key<Call<T>, T>()
builder.fetcher(object : Fetcher<T, Call<T>> {
override suspend fun fetch(myCall: Call<T>) = myCall.await()
})
//add Configuration for parsing/chaching
annotations
.map { it.javaClass }
.forEach {
when (it) {
Persister::class.java -> addPersister(builder, call)
RefershOnStale::class.java -> builder.refreshOnStale()
}
}
store = builder.open()
}
return store!!
}
override fun responseType() = responseType
private fun addPersister(builder: RealStoreBuilder<T, T, Call<T>>, call: Call<T>) =
builder.persister(object : DiskRead<T, Call<T>> {
override suspend fun read(key: Call<T>): T? {
val key = call.request()
return null
}
}, object : DiskWrite<T, Call<T>> {
override suspend fun write(key: Call<T>, raw: T): Boolean = true
})
}
}
suspend fun <T : Any> Call<T>.await(): T {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
cancel()
}
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
val body = response.body()
if (body == null) {
val invocation = call.request().tag(Invocation::class.java)!!
val method = invocation.method()
val e = KotlinNullPointerException("Response from " +
method.declaringClass.name +
'.' +
method.name +
" was null but response body type was declared as non-null")
continuation.resumeWithException(e)
} else {
continuation.resume(body)
}
} else {
continuation.resumeWithException(HttpException(response))
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment