Retrofit: How to send a POST request with constant fields?

后端 未结 4 1302
旧时难觅i
旧时难觅i 2021-02-19 09:29

I want to sent a simple POST request with one actual parameter:

@POST(\"/token\")
@FormUrlEncoded
void extendSession(@Field(\"refresh_token\")final String refres         


        
4条回答
  •  醉话见心
    2021-02-19 10:32

    A bit of an old question but after thinking about this myself for a while, this is what I came up with. I would love to hear any thoughts!

    As is standard, I have my @POST defined in an interface called AuthWebservice:

    interface AuthWebservice {
    
        @POST("oauth/token")
        @FormUrlEncoded
        fun refreshToken(
            @Field("grant_type")
            grantType: GrantType,
    
            @Field("client_id")
            clientId: String,
    
            @Field("client_secret")
            clientSecret: String,
    
            @Field("refresh_token")
            refreshToken: String
        ): Call
    
    }
    

    [NOTE: I'm using Dagger for dependency injection but the following logic would work no matter where you instantiate your webservice]

    In my NetworkModule I have the following to get an instance of AuthWebservice:

    @Module
    class NetworkModule {
        ...
    
        @Provides
        @Singleton
        fun providesAuthWebservice(
            retrofit: Retrofit
        ): AuthWebservice = retrofit.create(AuthWebservice::class.java)
    
        ...
    }
    

    So here's my solution: I also include the following method definition inside AuthWebservice:

    fun refreshToken(refreshToken: String): Call
    

    Note there are no annotations of any sort, and the method returns the same data type as the version that includes all of the arguments. This will compile, but obviously it will fail at runtime if you try to call it, something akin to the following:

    java.lang.IllegalArgumentException: HTTP method annotation is required (e.g., @GET, @POST, etc.). for method AuthWebservice.refreshToken

    Now I make a class called AuthWebserviceWrapper that takes an instance of AuthWebservice. In most cases it just calls the corresponding method on the base instance, except for the method I just added above:

    class AuthWebserviceWrapper(private val base: AuthWebservice) : AuthWebservice {
    
        // Just call the base method.
        override fun refreshToken(
            grantType: GrantType,
            clientId: String,
            clientSecret: String,
            refreshToken: String
        ): Call = base.refreshToken(
            grantType, 
            clientId,
            clientSecret, 
            refreshToken)
    
        // Call the base method with defaults!
        override fun refreshToken(refreshToken: String): Call = 
            base.refreshToken(
                GrantType.REFRESH_TOKEN, // Default value 
                BuildConfig.MY_CLIENT_ID, // Default value 
                BuildConfig.MY_CLIENT_SECRET,  // Default value
                refreshToken
            )
    
    }
    

    And finally, back in NetworkModule, I wrap Retrofit's default implementation like so:

    @Module
    class NetworkModule {
        ...
    
        @Provides
        @Singleton
        fun providesAuthWebservice(
            retrofit: Retrofit
        ): AuthWebservice = AuthWebserviceWrapper(retrofit.create(AuthWebservice::class.java))
    
        ...
    }
    

    Now when I call refreshToken I get my method with default values:

    class MyClass @Inject constructor(authWebservice: AuthWebservice) {
    
        fun doSomething(refreshToken: String) {
           val call = authWebservice.refreshToken(refreshToken)
        }
    
    }
    

    This does of course introduce some boilerplate, of which I am not a fan, but I think ultimately it's the cleanest way of making a webservice call without introducing the need for @Body or @FieldMap.

    Anyway, that's my story and I'm sticking to it.

提交回复
热议问题