Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

@POST("/token")
@FormUrlEncoded
void extendSession(@Field("refresh_token")final String refreshToken);

But this request should also send some constant values requested by the server such as client_id, client_secret and grant_type which are constant and should not be part of the application API.

What is the best way to do this?

like image 541
Avi Shukron Avatar asked Nov 02 '14 07:11

Avi Shukron


People also ask

What is retrofit annotation?

Retrofit is a type-safe REST client for Android, Java and Kotlin developed by Square. The library provides a powerful framework for authenticating and interacting with APIs and sending network requests with OkHttp.

What is field in retrofit?

@Body – Sends Java objects as request body. @Field – send data as form-urlencoded. This requires a @FormUrlEncoded annotation attached with the method. The @Field parameter works only with a POST. @Field requires a mandatory parameter.


2 Answers

It is matter of your approach. If you have the constants you can build a default Map of values required for your call. @FieldMap will be suitable for building a Map with all your required fields

private void extendSession(String token){
   Map params = buildDefaultParams();
   params.put("refreshToken", token);
   getRestAdapter().create(MyApi.class).extendsSession(params);
}
private Map buildDefaultParams(){
   Map defaults = new HashMap();
   defaults.put("client_id", CLIENT_ID);
   defaults.put("client_secret", CLIENT_SECRET);
   defaults.put("grant_type", GRANT_TYPE);
   return defaults;
}
  /**then you change your interface to this **/ 
    @POST("/token")
    @FormUrlEncoded
    void extendSession(@FieldMap() Map refreshToken);
like image 117
Nikola Despotoski Avatar answered Oct 16 '22 18:10

Nikola Despotoski


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<AccessToken>

}

[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<AccessToken>

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<AccessToken> = base.refreshToken(
        grantType, 
        clientId,
        clientSecret, 
        refreshToken)

    // Call the base method with defaults!
    override fun refreshToken(refreshToken: String): Call<AccessToken> = 
        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.

like image 2
Codepunk Avatar answered Oct 16 '22 16:10

Codepunk