Skip to content

Instantly share code, notes, and snippets.

@jutikorn
Created July 11, 2018 09:50
Show Gist options
  • Save jutikorn/698b2688370e7d4a408353b7cf761e19 to your computer and use it in GitHub Desktop.
Save jutikorn/698b2688370e7d4a408353b7cf761e19 to your computer and use it in GitHub Desktop.
import android.util.Log
import retrofit2.Response
import java.util.regex.Pattern
/**
* Common class used by API responses.
* @param <T> the type of the response object
</T> */
@Suppress("unused") // T is used in extending classes
sealed class ApiResponse<T> {
companion object {
fun <T> create(error: Throwable): ApiErrorResponse<T> {
return ApiErrorResponse(error.message ?: "unknown error")
}
fun <T> create(response: Response<T>): ApiResponse<T> {
return if (response.isSuccessful) {
val body = response.body()
if (body == null || response.code() == 204) {
ApiEmptyResponse()
} else {
ApiSuccessResponse(
body = body,
linkHeader = response.headers()?.get("link")
)
}
} else {
val msg = response.errorBody()?.string()
val errorMsg = if (msg.isNullOrEmpty()) {
response.message()
} else {
msg
}
ApiErrorResponse(errorMsg ?: "unknown error")
}
}
}
}
/**
* separate class for HTTP 204 resposes so that we can make ApiSuccessResponse's body non-null.
*/
class ApiEmptyResponse<T> : ApiResponse<T>()
data class ApiSuccessResponse<T>(
val body: T,
val links: Map<String, String>
) : ApiResponse<T>() {
constructor(body: T, linkHeader: String?) : this(
body = body,
links = linkHeader?.extractLinks() ?: emptyMap()
)
val nextPage: Int? by lazy(LazyThreadSafetyMode.NONE) {
links[NEXT_LINK]?.let { next ->
val matcher = PAGE_PATTERN.matcher(next)
if (!matcher.find() || matcher.groupCount() != 1) {
null
} else {
try {
Integer.parseInt(matcher.group(1))
} catch (ex: NumberFormatException) {
Log.w("Warning" ,"cannot parse next page from $next")
null
}
}
}
}
companion object {
private val LINK_PATTERN = Pattern.compile("<([^>]*)>[\\s]*;[\\s]*rel=\"([a-zA-Z0-9]+)\"")
private val PAGE_PATTERN = Pattern.compile("\\bpage=(\\d+)")
private const val NEXT_LINK = "next"
private fun String.extractLinks(): Map<String, String> {
val links = mutableMapOf<String, String>()
val matcher = LINK_PATTERN.matcher(this)
while (matcher.find()) {
val count = matcher.groupCount()
if (count == 2) {
links[matcher.group(2)] = matcher.group(1)
}
}
return links
}
}
}
data class ApiErrorResponse<T>(val errorMessage: String) : ApiResponse<T>()
import com.jutikorn.eddieplayer.common.structure.SingleLiveEvent
import retrofit2.Call
import retrofit2.CallAdapter
import retrofit2.Callback
import retrofit2.Response
import java.lang.reflect.Type
/**
* A Retrofit adapter that converts the Call into a SingleLiveEvent of ApiResponse.
* @param <R>
</R> */
class SingleLiveEventCallAdapter<R>(private val responseType: Type) :
CallAdapter<R, SingleLiveEvent<ApiResponse<R>>> {
override fun responseType() = responseType
override fun adapt(call: Call<R>): SingleLiveEvent<ApiResponse<R>> =
SingleLiveEvent<ApiResponse<R>>().apply {
call.enqueue(object : Callback<R> {
override fun onResponse(call: Call<R>, response: Response<R>) {
this@apply.postValue(ApiResponse.create(response))
}
override fun onFailure(call: Call<R>, throwable: Throwable) {
this@apply.postValue(ApiResponse.create(throwable))
}
})
}
}
import com.jutikorn.eddieplayer.common.structure.SingleLiveEvent
import retrofit2.CallAdapter
import retrofit2.CallAdapter.Factory
import retrofit2.Retrofit
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
class SingleLiveEventCallAdapterFactory : Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (Factory.getRawType(returnType) != SingleLiveEvent::class.java) {
return null
}
val observableType = Factory.getParameterUpperBound(0, returnType as ParameterizedType)
val rawObservableType = Factory.getRawType(observableType)
if (rawObservableType != ApiResponse::class.java) {
throw IllegalArgumentException("type must be a resource")
}
if (observableType !is ParameterizedType) {
throw IllegalArgumentException("resource must be parameterized")
}
val bodyType = Factory.getParameterUpperBound(0, observableType)
return SingleLiveEventCallAdapter<Any>(bodyType)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment