Search code examples
androidkotlinretrofit2okhttp

Handle http request path changes in runtime with retrofit


I have this api usage now:

App.API.foo(mapOf("a" to a, "b" to b)).enqueue(responseHandler)

// my api class
@POST("/foo-v1")
fun foo(@Body map: Map<String, String>): Call<Response>

Now I have new api path: "/foo-v2" and new baseurl that defined in runtime.

What can be my best way to do changes in runtime? option that I think: to add @Path("version") version: String to foo function will be like this:

@POST("/foo-{version}") 
fun foo(@Body map: Map<String, String>, @Path("version") version: String): Call<Response>

its ok for one single request but in case when changed a lot of requests its not so good.

Ps another question, what can be done when for some requests need also add new path like @POST("/auth/foo-{version}")


Solution

  • I originally commented because I'm short on time, but I wanted to offer some quick code to illustrate the point.

    Given a service like this:

    interface ChangePathService {
        @POST(".")
        fun foo(
            @Header("path") path: String,
            @Body map: Map<String, String>
        ): Call<Response>
    }
    

    You can set your endpoint path at run time using an interceptor, which is applied to your OkHttpClient.Builder.

    class ChangePathInterceptor(
        private val baseUrl: String,
    ): Interceptor {
        override fun intercept(chain: Interceptor.Chain): Response {
            // This request is has "." as the path. We want to change it!
            val request = chain.request()
    
            /**
             * Get the path argument via the headers. I exit early here 
             * with the original request going through the network but you
             * can throw an exception or whatever.
             */
            val path = request.header("path")
                ?: return chain.proceed(request)
    
            val body = request.body // don't forget to apply your request body
    
            /**
             * Here's where you create your new request at runtime!
             */
    
            val newRequest = Request.Builder()
                .url(baseUrl+path)
                .headers(request.headers)
                .apply {
                    if(body == null) this else post(body)
                }
                .build()
    
            return chain.proceed(newRequest)
        }
    } 
    

    EDIT: The path can be managed at runtime from the service endpoint call site. For example, a repository:

    class SomeRepository(
        private val service: ChangePathService,
    ) {
        
        private fun getFooVersion(): String {
            // do your version logic here.
        }
    
        fun callFoo(map: Map<String, String>) {
    
            val version: String = getFooVersion() // this could be calculated once at the repository level depending on your need
    
            val call = service.foo(
                path = "/foo-$version", // interpolate version here
                body = map,
            )
            
            call.enqueue(responseHandler)
        }
    
        ...
    }