Skip to content

Instantly share code, notes, and snippets.

@aoriani
Last active August 13, 2023 16:48
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 aoriani/f20755828988965af3dcb0c14b7c8666 to your computer and use it in GitHub Desktop.
Save aoriani/f20755828988965af3dcb0c14b7c8666 to your computer and use it in GitHub Desktop.
A simple implementation of a Retrofit-like library
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Proxy
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class GET(val baseUrl: String)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class Query(val parameterName: String)
interface Call<out T> {
fun execute(): T
}
class SimpleRetrofit {
val objectMapper: ObjectMapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
val httpClient = OkHttpClient()
private fun createRequest(method: Method, args: Array<Any?>): Request {
val baseUrl = method.getAnnotation(GET::class.java).baseUrl
val paramNames = method.parameterAnnotations.flatten().map { (it as Query).parameterName }
val url = HttpUrl.parse(baseUrl).newBuilder().apply {
paramNames.forEachIndexed { index, paramName -> addQueryParameter(paramName, args[index].toString()) }
}.build()
return Request.Builder().url(url).build()
}
private fun <T> createCall(request: Request, responseClass: Class<T>): Call<T> {
return object : Call<T> {
override fun execute(): T {
val response = httpClient.newCall(request).execute().body().string()
return objectMapper.readValue(response, responseClass)
}
}
}
private fun extractResponseType(method: Method) = (method.genericReturnType as ParameterizedType).actualTypeArguments[0] as Class<*>
fun <T> createService(serviceClass: Class<T>): T = Proxy.newProxyInstance(serviceClass.classLoader, arrayOf(serviceClass)) {
thiz: Any, method: Method, args: Array<Any?> ->
val request = createRequest(method, args)
val responseType = extractResponseType(method)
createCall(request, responseType)
} as T
}
data class Weather(val main: Main)
data class Main(val temp: Double)
data class UvIndex(val value: Double)
interface OpenWeatherMapApi {
@GET("http://samples.openweathermap.org/data/2.5/weather")
fun getWeather(@Query("q") city: String, @Query("appid") apiKey: String): Call<Weather>
@GET("http://samples.openweathermap.org/data/2.5/uvi")
fun getUvIndex(@Query("lat") lat: Double, @Query("lon") lon: Double, @Query("appid") apiKey: String): Call<UvIndex>
}
fun main(args: Array<String>) {
val API_KEY = "YOUK KEY"
val service = SimpleRetrofit().createService(OpenWeatherMapApi::class.java)
val weather = service.getWeather("London", API_KEY).execute()
val uvIndex = service.getUvIndex(37.75, -122.37, API_KEY).execute()
println(weather)
println(uvIndex)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment