13

Image Multipart in class type object.

case 1. (Which I had done)

Service params:

{"id":"1","name":"vishal","image/file":""} 

At that time my API of Retrofit

@Multipart
@POST("webservice")
Call<SignUpResp> loadSignupMultipart(@Part("description") RequestBody description, @Part MultipartBody.Part file, @QueryMap HashMap<String, String> params);

case 2. (Where I have Problem) with @Body class<UploadwithImage>

{
    "methodName":"submitLevel1Part2Icon",
    "userid":"150",
    "headerData":{
        "fiction":{
            "icon_type":"1",
            "icon_id":"3"},
        "nonfiction":{
            "icon_type":"2",
            "icon_id":"4"},
        "relation":{
            "icon_type":"3",
            "icon_id":"0",
            "name":"Ronak",
            "relative_image":"<File>",
            "relation_id":"3"},
        "self":{
            "icon_type":"4",
            "icon_id":"0"}
    }
}

I am trying this API

 @Multipart
 @POST("webservice")
 Call<SubmitLevel1Part2IconResp> loadLevel1halfIconswithImage(@Part("description") RequestBody description, @Part MultipartBody.Part file, @Body UploadwithImage uploadImage);

Java side

    /**
     * code for multipart
     */
     // create RequestBody instance from file
     RequestBody requestFile =  RequestBody.create(MediaType.parse("multipart/form-data"), fileUpload);

     // MultipartBody.Part is used to send also the actual filename
     MultipartBody.Part body =  MultipartBody.Part.createFormData("methodName[headerData][relation][relative_image]", fileUpload.getName(), requestFile);

     // add another part within the multipart request
     String descriptionString = "hello, this is description speaking";
     RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);

    call = service.loadLevel1halfIconswithImage(description, body, levelOneHalfIcons);

I don't know why but it returns error like:

"@Body parameters cannot be used with form or multi-part encoding"

Any Help would Be Appreciated.

Ravi
  • 34,851
  • 21
  • 122
  • 183
Vishal Patel
  • 2,931
  • 3
  • 24
  • 60

8 Answers8

15

Change your method to

@Multipart
@POST("users/{id}/user_photos")
Call<models.UploadResponse> uploadPhoto(@Path("id") int userId, @PartMap Map<String, RequestBody> params);

Now to create your request parameters,

//All the String parameters, you have to put like
Map<String, RequestBody> map = new HashMap<>();
map.put("methodName", toRequestBody(methodName));
map.put("userid", toRequestBody(userId));
map.put("relation", toRequestBody(relation));
map.put("icon_type", toRequestBody(iconType));
map.put("icon_id", toRequestBody(iconId));
map.put("name", toRequestBody(name));
map.put("relation_id", toRequestBody(relationId));

//To put your image file you have to do
File file = new File("file_name");
RequestBody fileBody = RequestBody.create(MediaType.parse("image/png"), file);
map.put("relative_image\"; filename=\"some_file_name.png\"", fileBody);

// This method  converts String to RequestBody
public static RequestBody toRequestBody (String value) {
    RequestBody body = RequestBody.create(MediaType.parse("text/plain"), value);
    return body ;
}

//To send your request
call = service.loadLevel1halfIconswithImage(description, params);

In case you do not want to use PartMap, you can simply pass them as parameters. Check my answer https://stackoverflow.com/a/37052548/1320616 to get some clue on sending image file with request.

Community
  • 1
  • 1
Ankit Aggarwal
  • 5,317
  • 2
  • 30
  • 53
  • Hi, I have Update my question, sorry, I wasn't write full json request prior, can you guide me. – Vishal Patel May 06 '16 at 13:51
  • 1
    I am not sure but as far as I know, I don't think it is possible. Multipart requests always go as form data instead of requestBody. Here you are trying to send a file inside a json. You only think whether it is possible to attach a file to a json. I don't think you can even replicate this api request in postman or some other rest client. – Ankit Aggarwal May 06 '16 at 14:27
  • 'Partmap' works fine in case we need to push String data, how can we push primitive data? I am not able to create 'RequestBody' using 'Integer' or 'boolean'. – Lovish Sindhwani Jun 24 '22 at 06:52
  • 1
    @LovishSindhwani check this answer https://stackoverflow.com/a/47627526/1320616 – Ankit Aggarwal Jun 28 '22 at 07:08
11

As simple way, I have done like this:

I have tested by changing

Call<Result> resultCall = service.uploadImage(body); 

to

Call<Result> resultCall = service.uploadImage(body, result); where result is

Result.java class (Response) of my API:

public class Result {

    @SerializedName("result")
    @Expose
    private String result;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @SerializedName("value")
    @Expose
    private String value;

    /**
     * @return The result
     */
    public String getResult() {
        return result;
    }

    /**
     * @param result The result
     */
    public void setResult(String result) {
        this.result = result;
    }

}

and created object like:

Result result = new Result();
result.setResult("success");
result.setValue("my value");

You can change your class as per your need then pass object when you send request. So your ApiService class will be like:

ApiService.java

/**
 * @author Pratik Butani on 23/4/16.
 */
public interface ApiService {

    /*
    Retrofit get annotation with our URL
    And our method that will return us the List of Contacts
    */
    @Multipart
    @POST("upload.php")
    Call<Result> uploadImage(@Part MultipartBody.Part file, @Part("result") Result result);

}

and My PHP code is:

<?php

    $file_path = "";
    $var = $_POST['result']; //here I m getting JSON

    $file_path = $file_path . basename( $_FILES['uploaded_file']['name']);
    if(move_uploaded_file($_FILES['uploaded_file']['tmp_name'], $file_path)) {
        $result = array("result" => "success", "value" => $var);
    } else{
        $result = array("result" => "error");
    }

    echo json_encode($result);

?>

Hope it will helps you. Thank you.

Pratik Butani
  • 60,504
  • 58
  • 273
  • 437
  • 2
    Thanks man this is a really nice way to deal with the situation. +1 – Gaurav Sarma Oct 18 '16 at 20:39
  • Everything works great. Problem arises while adding `@Part MultipartBody.Part file`. It throws `Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $`. NOTE: I am using custom `converterFactory` (by implementing Gson). Could that be problem? – Farid May 09 '17 at 21:08
  • 1
    @FARID Check your response in both case, may you are getting error from server side. – Pratik Butani May 10 '17 at 03:57
  • @PratikButani yes, thank you that was the problem. Solved ) – Farid May 10 '17 at 14:03
  • And one more question. Do you know any way to send the @part object (Result Object, in your case) as JSON not as POST parameter? – Farid May 10 '17 at 14:08
7

You can also use a Map with RequestBody as value and string as keys to add parameters and you can send this using Multipart and PartMap.

Check the following code, hope it will help :

@Multipart
@POST("/add")
Call<ResponseBody> addDocument(@PartMap Map<String,RequestBody> params);

Map<String, RequestBody> map = new HashMap<>();

map.put("user_id", RequestBody.create(MediaType.parse("multipart/form-data"), SessionManager.getInstance().getCurrentUserId()));
map.put("doc_name", RequestBody.create(MediaType.parse("multipart/form-data"), CommonUtils.removeExtension(textFile.getName())));
map.put("doc_category", RequestBody.create(MediaType.parse("multipart/form-data"), category));
map.put("doc_image_file", RequestBody.create(MediaType.parse("multipart/form-data"), imageFile));
map.put("doc_text_content", RequestBody.create(MediaType.parse("multipart/form-data"), body));
map.put("doc_update_time", RequestBody.create(MediaType.parse("multipart/form-data"), "" + new Date(textFile.lastModified())));
Sandip Fichadiya
  • 3,430
  • 2
  • 21
  • 47
gpuser
  • 1,143
  • 1
  • 9
  • 6
5

We can add all request parameter in multipart body builder with specified type like in below one image file. I have set media type parse multipart/form-data and some other parameter I have set media type parse text/plain. This builder will build to make Multipart Body and can send by using body annotation in multipart body.

@Multipart
@POST("user/update")
Call<ResponseBody> addDocument(@@Part MultipartBody file);


final MultipartBody.Builder requestBodyBuilder = new MultipartBody.Builder()
      .setType(MultipartBody.FORM);

requestBodyBuilder.addFormDataPart("doc_image_file", imageFile.getName(),
      RequestBody.create(MediaType.parse("multipart/form-data"), imageFile));
requestBodyBuilder.addFormDataPart("user_id", null, RequestBody.create(MediaType.parse("text/plain"),"12"));
requestBodyBuilder.addFormDataPart("doc_name", null, RequestBody.create(MediaType.parse("text/plain"),"myfile"));
requestBodyBuilder.addFormDataPart("doc_category", null, RequestBody.create(MediaType.parse("text/plain"),category));
requestBodyBuilder.addFormDataPart("doc_image_file", imageFile.getName(),RequestBody.create(MediaType.parse("multipart/form-data"),imageFile));
requestBodyBuilder.addFormDataPart("doc_text_content", null, RequestBody.create(MediaType.parse("text/plain"),body));
RequestBody multipartBody = requestBodyBuilder.build();
Sandip Fichadiya
  • 3,430
  • 2
  • 21
  • 47
gpuser
  • 1,143
  • 1
  • 9
  • 6
  • RequestBody multipartBody = requestBodyBuilder.build(); actually this line MultpartBdy or requestBdy – Siru malayil Jun 11 '21 at 10:25
  • How exactly is this used? java.lang.IllegalArgumentException: @Part annotation must supply a name or use MultipartBody.Part parameter type. (parameter #2) – TheRealChx101 Sep 28 '22 at 05:32
2
Here is my json request format is :
{
"task":{
"category_id":"1",
"price":"10",
"description":"1",
"task_videos_attributes":[
{
"link":"video file goes here",
"size":"100x100"
}
]
}
}



// my request becomes 
 HashMap<String, RequestBody> task = new HashMap();          
  task.put("task[category_id]", createPartFromString(categoryId));
  task.put("task[price]", createPartFromString("" + etPrice.getText().toString()));
            task.put("task[description]", createPartFromString("" + etDescription.getText().toString()));


// for videos file list
  final List<MultipartBody.Part> body = new ArrayList<>();
  for (int i = 0; i < videos.size(); i++) {

 task.put("task[task_videos_attributes][" + i+ "][size]", createPartFromString("100x100"));

 File videoFile = new File(videos.get(i));
                        RequestBody requestBody = RequestBody.create(MediaType.parse("video/mp4"), videoFile);
                        MultipartBody.Part fileToUpload = MultipartBody.Part.createFormData("task[task_videos_attributes][" + i + "][link]", videoFile.getName(), requestBody);
                        body.add(fileToUpload);

}


// here is a final call
  new RestClient(this).getInstance().get().postTask(body, task).enqueue(callback);




// This function converts my string to request body
   @NonNull
    private RequestBody createPartFromString(String descriptionString) {
        if (descriptionString == null)
            return RequestBody.create(MultipartBody.FORM, "");
        return RequestBody.create(
                MultipartBody.FORM, descriptionString);

    }

Hope this helps you...

Jatin
  • 1,650
  • 3
  • 17
  • 32
  • can you please show me the format of data you are sending to the server? – Rahul Sep 27 '18 at 05:15
  • is this format only used for php server's request?l have tried it but not working – Rahul Sep 28 '18 at 06:22
  • please check this post https://stackoverflow.com/questions/52520520/how-to-add-list-of-object-i-e-userdata-type-to-multipartbody-in-okhttpclient?noredirect=1#comment92036626_52520520 – Rahul Sep 28 '18 at 06:22
  • My files end up getting uploading in a wrong way. Spring boot sees the fields as strings instead even when image/jpeg is set. – TheRealChx101 Sep 28 '22 at 05:33
0

Just follow how the web browser is doing multipart. They put nested keys in "[]" and give key to the images too.

Call<SubmitLevel1Part2IconResp> loadLevel1halfIconswithImage(@Part("headerdata[relation][icon_type]") RequestBody icon_type, @Part("headerdata[relation][name]") RequestBody name, @Part MultipartBody.Part file);

And then in java

 // MultipartBody.Part is used to send also the actual filename
 MultipartBody.Part body =  MultipartBody.Part.createFormData("headerdata[relation][relative_image]", fileUpload.getName(), requestFile);



call = service.loadLevel1halfIconswithImage(icon_type, name, body);
andylee
  • 255
  • 2
  • 6
0

https://www.linkedin.com/pulse/retrofit-2-how-upload-multiple-files-server-mahesh-gawale

I guess the best answer to this question can be found here. It worked perfectly for me.

This is the example of uploading an array of files using retrofit in Android.

This is how the service will look like

public interface ApiService {

    @POST("/event/store")
    Call<ResModel> event_store(@Body RequestBody file);
}

This is how the Client class look like

public class ApiClient   {
    public static final String API_BASE_URL = "api base url";

    private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

    private static Retrofit.Builder builder = new Retrofit.Builder().baseUrl(API_BASE_URL).addConverterFactory(GsonConverterFactory.create());

    public static ApiService createService(Class<ApiService> serviceClass)
    {
        Retrofit retrofit = builder.client(httpClient.build()).build();
        return retrofit.create(serviceClass);
    }
}

Upload like this in activity or fragment or where you want

    ApiService service = ApiClient.createService(ApiService.class);

    MultipartBody.Builder builder = new MultipartBody.Builder();
    builder.setType(MultipartBody.FORM);


    builder.addFormDataPart("event_name", "xyz");
    builder.addFormDataPart("desc", "Lorem ipsum");

    // Single Image
    builder.addFormDataPart("files",file1.getName(),RequestBody.create(MediaType.parse("image/*"), file1));

    // Multiple Images 
    for (int i = 0; i <filePaths.size() ; i++) {
            File file = new File(filePaths.get(i));
            RequestBody requestImage = RequestBody.create(MediaType.parse("multipart/form-data"), file);
            builder.addFormDataPart("event_images[]", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file));
            }


    MultipartBody requestBody = builder.build();
    Call<ResModel> call = service.event_store(requestBody);
    call.enqueue(new Callback<ResponseBody>() {
         @Override
         public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
             Toast.makeText(getBaseContext(),"All fine",Toast.LENGTH_SHORT).show();
         }

         @Override
         public void onFailure(Call<ResponseBody> call, Throwable t) {
            Toast.makeText(getBaseContext(),t.getMessage(),Toast.LENGTH_SHORT).show();
         }
     });

Note: filePaths.size() is a Arraylist of pickup Images Paths. I hope this post is useful to you. kindly share your feedback as a comment here.

OhhhThatVarun
  • 3,981
  • 2
  • 26
  • 49
0

this works for me.

What I did was add every additional params using:

MultipartBody.Part Partname = MultipartBody.Part.createFormData("ParamName", "Value");

Mabe you don't need to create another body, but just add others params apart from the file or whatever you are sending. finally at the interface I put as a params every bodypart that I need.

@Multipart
@POST("api/service/uploadVideo")
Call<ResponseBody> uploadVideoToServer(
        @Part MultipartBody.Part video,
        @Part MultipartBody.Part param2,
        @Part MultipartBody.Part param3 ....
);