58

I am trying to make POST request using the Retrofit 2. The request type is form-data NOT application/x-www-form-urlencoded.

I am only posting data not the files in the request and the response is in the form of JSON.

I have tried @FormUrlEncoded, @Multipart but it is not working.

I have tried following request

1. First Attempt

@FormUrlEncoded
@POST("XXXX")
Call<PlanResponse> getPlanName(@Field(Constants.ACTION_ID) String actionId, @Field(Constants.OFFER_CODE) String offerCode);

2. Second Attempt

@Headers({"Content-Type: multipart/form-data","Content-Type: text/plain"})
@FormUrlEncoded
@POST("XXXX")
Call<PlanResponse> getPlans(@Body @FieldMap(encoded = false) Map<String, String> data);

3. Third Attempt

@Headers("Content-Type: multipart/form-data")
@Multipart
@POST("XXXX")
Call<PlanResponse> myPlans(@Part(Constants.ACTION_ID) String actionId, @Part(Constants.OFFER_CODE) String offerCode);

I am only getting the body as null. It is working with the POSTMAN.

I have also search about form-data and application/x-www-form-urlencoded and found that if the data is binary then use form-data and if data is ASCII then use application/x-www-form-urlencoded

I am trying find Is form-data is not supported by the Retrofit?

POSTMAN request

Cache-Control: no-cache
Postman-Token: XXXXXXXXXXXX-XXXXXXXXXXXX-XXXXXXXXXXXX-XXXXXXXXXXXX-XXXXXXXXXXXX
Content-Type: multipart/form-data; boundary=----    WebKitFormBoundaryXXXXXXXXXXXX


 ----WebKitFormBoundaryXXXXXXXXXXXX
Content-Disposition: form-data; name="actionId"

1000
 ----WebKitFormBoundaryXXXXXXXXXXXX
Content-Disposition: form-data; name="offerCode"

MYCODE
----WebKitFormBoundaryXXXXXXXXXXXX

I can only add HTTP Generated code snipped from POSTMAN

AkshayT
  • 2,901
  • 2
  • 28
  • 32

8 Answers8

80

Here's another Solution using request body:

RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("param1", param1)
        .addFormDataPart("param2", param2)
        .build();

apiInterface.somePostMethod(requestBody).enqueue(
    //onResponse onFailure methods
);

here's my api inteface POST method

@POST("somePostMethod")
Call<ResponseBody> somePostMethod(@Body RequestBody body);

Hope it helps.

Monster Brain
  • 1,950
  • 18
  • 28
  • 1
    For mi this try works, I think it is cleaners when you had a long list of form data parameters. – Jimmy Alvarez Dec 27 '19 at 19:13
  • 1
    This worked even when you are trying to upload image as encoded string to the server. Thanks for the clean code. – Jitu Jul 07 '21 at 18:34
56

In retrofit 2.0 to perform POST request like above, you should use RequestBody type for your parameter like this.

@Multipart
@POST("XXXX")
Call<PlanResponse> myPlans(@Part(Constants.ACTION_ID) RequestBody actionId, @Part(Constants.OFFER_CODE) RequestBody offerCode);

And here how to get requestBody from String.

String somevalue = "somevalue";
RequestBody body = RequestBody.create(MediaType.parse("text/plain"), somevalue);
ikhsanudinhakim
  • 1,554
  • 16
  • 23
  • Your answer is right, Though I have edited your answer for some correction `@Part(Constants.OFFER_CODE) RequestBody offerCode`. – AkshayT Jun 15 '16 at 10:25
  • I was trying a POST request using the Retrofit 2. and the request type was form-data. tried using `@Field, @Query, @Body`. But `@Part` with RequestBody was it. Thanks! – anoo_radha Jul 18 '17 at 14:31
  • Not working in my case. getting the same issue.. Please check this link https://stackoverflow.com/questions/65125422/retrofit-calling-the-magneto-api-using-formurlencoded-getting-issue-while-sendi – Peter Dec 11 '20 at 13:59
  • what import did you use for import create? – Qube Apr 13 '21 at 04:00
8

I wanted to pass an array of ids to an existing request.

I tried several variants from here, Retrofit - Send request body as array or number, How to send PUT request with retrofit string and array list of model I need to use URL encoded, but they didn't work. Then I tried android retrofit send array as x-www-form-urlencoded.

I added [] to a list parameter and List to it's type:

@FormUrlEncoded
@POST("your_request/")
fun sendIds(
    @Field("token") token: String,
    @Field("city_id") cityId: Int?,
    @Field("description") description: String,
    @Field("ids[]") ids: List<Int>? // Add '[]' here.
): Deferred<YourResponse>

Then called it as usual (with Kotlin coroutines):

api.sendIds("f0123abc", null, "description", listOf(1, 2, 3)).await()

See also Is it possible to send an array with the Postman Chrome extension? to understand how it looks like in Postman.

CoolMind
  • 26,736
  • 15
  • 188
  • 224
4

form-data is supported for sure. I will make you clear using an example of typical signup process. First of all add a header

@FormUrlEncoded

in your user client. Use

@FieldMap

instead of direct objects. So your user-client code will something like this

@POST("signup/")
@FormUrlEncoded
Call<ResponseModel> signup(@FieldMap Map<String,String> params);

Now in your main activity, make a Hashmap all of your data like this,

 Map<String,String> params = new HashMap<String, String>();
    params.put("fullname", fullname);
    params.put("city", city);
    params.put("state",state);
    params.put("address",address);
    params.put("email",email);
    params.put("password1", password1);
    params.put("password2", password2);

Now simple pass this hashmap into the method like this

 Call<ResponseModel> call = service.signup(params);
    call.enqueue(new Callback<ResponseModel>() {
        @Override
        public void onResponse(Call<ResponseModel> call, Response<ResponseModel> response) {
            if (response.isSuccessful()) {
                Toast.makeText(SignUp.this,response.body.getData,Toast.LENGTH_SHORT).show();
                
            } else {
                Toast.makeText(SignUp.this, "Error : ", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void onFailure(Call<ResponseModel> call, Throwable t) {
            t.printStackTrace();
            Toast.makeText(SignUp.this, "Server Unavailable : "+t.toString(), Toast.LENGTH_SHORT).show();
        }
    });
Christopher Nolan
  • 930
  • 1
  • 11
  • 15
4

Here's another Solution using the request body form-data in Kotlin. This solution work for me in Kotlin.

val request = ServiceBuilder.buildService(TmdbEndpoints::class.java)
val requestBody: RequestBody = MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart("email", "abc@gmail.com")
      .addFormDataPart("password", "admin")
      .addFormDataPart("push_token", "token")
      .addFormDataPart("device_id", "1112222")
      .addFormDataPart("platform", "android")
      .addFormDataPart("device_name", "my device")
      .addFormDataPart("version", "1.2")
      .build()
            
val call = request.userFigLogin(requestBody)

call.enqueue(object : Callback<LoginResult> {
     override fun onFailure(call: Call<LoginResult>, t: Throwable) { }

     override fun onResponse(call: Call<LoginResult>,
          response: retrofit2.Response<LoginResult>) { }
})

You should use RequestBody type for your parameter like this.

@POST("api/login")
fun userFigLogin(@Body body: RequestBody): Call<LoginResult>
Sumit Pansuriya
  • 543
  • 10
  • 23
2

For Kotlin, This is another way of doing it. For api that do not accept FormUrEncoded data.

 fun login(email: String, password: String, grantType: String):                             

  Single<TokenModel> {
     var userNameB:RequestBody= 
       email.toRequestBody(email.toMediaTypeOrNull())
     var passwordB: RequestBody =
       password.toRequestBody(password.toMediaTypeOrNull())
     var grantTypeB: RequestBody =
       grantType.toRequestBody(grantType.toMediaTypeOrNull())

  return userApi.loginUSer(userNameB,passwordB,grantTypeB)
   .map { TokenModel(it.accessToken, it.refreshToken) }

}

Then.

 @Multipart
 @POST("auth/token/")
 fun loginUSer(
    @Part("username") request: RequestBody,
    @Part("password") passwordB: RequestBody,
    @Part("grant_type") grantTypeB: RequestBody
 ): Single<Token>
Amos Korir
  • 123
  • 6
1

just remove this from header defaultProperties["Content-Type"] = "application/json"

-1

I think this can help you

  @Multipart
@Headers( "Content-Type: application/x-www-form-urlencoded")
@POST("api/register")
fun postRegister(
    @Part("authtype") authtype: String,
    @Part("channel")channel : String,
    @Part("children")children : List<String>,
    @Part("names") names: List<String>,
    @Part("email") email: String,
    @Part("password")password : String,
    @Part("name") name: String,
    @Part("timezone") timezone: Int,
    @Part("timezone_name")timezone_name : String,
    @Part("token_device")token_device : String,
    @Part("imageData") imageData: String,
    @Part("mimeType") mimeType: String,
    @Part("extension") extension: String,
): Call<ResponseBase>