Retrofit 2 can't upload a file with two additional separate string parameters

29,182

Solution 1

I faced similar issue here: Android with Retrofit2 OkHttp3 - Multipart POST Error

I got my problem solved after taking @TommySM suggestion. If is still unclear to you, I think this is the solution:

@Multipart
@POST("jobDocuments/upload")
Call<ResponseBody> upload(
    @Part MultipartBody.Part file,
    @Part("folder") RequestBody folder,
    @Part("name")   RequestBody name);

File file = new File(fileUri.getPath());

// Assume your file is PNG
RequestBody requestFile =
        RequestBody.create(MediaType.parse("image/png"), file);

MultipartBody.Part fileData =
        MultipartBody.Part.createFormData("file", fileName, requestFile);

RequestBody folder = RequestBody.create(
        MediaType.parse("text/plain"),
        "LeadDocuments");

RequestBody name = RequestBody.create(
        MediaType.parse("text/plain"),
        fileName);

// finally, execute the request
Call<ResponseBody> call = service.upload(fileData, folder, name);

The important part is to use MediaType.parse("text/plain") for MediaType of String parameter (I believe your case is: folder & name parameter), using okhttp3.MultipartBody.FORM is a mistake.

See these screenshots for the comparison:

1) Problematic POST

enter image description here

2) Correct POST

enter image description here

Solution 2

So, hope it's no too late, and if so - it might help somebody else :) my 2 cents about this is after having the same problem a while back:

The service definition, using @Part only (modify field names according to what your server expects)

//Single image MultiPart
@Multipart
@POST("user/imageupload")
Call<ResponseBody> upload(@Part("userfile") RequestBody file, @Part("userid") RequestBody description);

And for the magic trick, I refer to both parts just as RequestBody, using the MediaType.parse() to each one with it's own type, relying on the @Multipart definition for the request itself, no need for form-data stuff, the same works for multiple files, and multiple fields:

private static final String IMG_JPEG = "image/jpeg";
private static final String TXT_PLAIN = "text/plain";

public void uploadImageMultipart(Uri uri, final CustomEventListener<String> listener)
{
    RequestBody fileBody;
    RequestBody textBody;
    File file = new File(uri.getPath());
    Call<ResponseBody> requestCall;

    fileBody = RequestBody.create(okhttp3.MediaType.parse(IMG_JPEG), file);
    textBody = RequestBody.create(okhttp3.MediaType.parse(TXT_PLAIN), String.valueOf(SettingsManager.getUserID()));

      requestCall = serviceCaller.upload(fileBody, textBody);
      requestCall.enqueue(new Callback<ResponseBody>()
      {
        @Override
        public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> rawResponse)
        {
            try
            {
                String response = rawResponse.body().string();
                //from here it's your show....
                listener.getResult("Got it");
            }
            catch (Exception e)
            {
                        e.printStackTrace();
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable throwable)
        {

        }
    });
}

(I use a listener to return the callback response to a different part of the app (e.g. a calling activity)).

This definitely sends the file/s and the text field, other problems would probably stem from the server side.

Hope this Helps!

Solution 3

Interface for Retrofit (API)

public interface API {

    String NAME = "name";
    String FOLDER = "folder";
    @Multipart
    @POST("/api/jobDocuments/upload")
    Call<JsonResponse> uploadFile(
        @Part(NAME) String name,
        @Part(FOLDER) String folder,
        @Part MultipartBody.Part file);
}

Response Class (JsonResponse)

public class JsonResponse {

    @SerializedName("$id")
    public String id;
    @SerializedName("Message")
    public String message;
    @SerializedName("Path")
    public String path;

    public JsonResponse(String id, String message, String path) {
        this.id = id;
        this.message = message;
        this.path = path;
    }
}

API call from application

Retrofit retrofit;

private void postImage() {
    String URL = "http://www.dgheating.com/";

    //your file location
    File file = new File(Environment.getExternalStorageDirectory() + "/Image.png");

    //parameters
    String NAME = file.getName();
    String FOLDER = "LeadDocuments";
    String FILE = "file";

    retrofit = new Retrofit.Builder()
            .baseUrl(URL)
            .addConverterFactory(GsonConverterFactory.create(new Gson()))
            .build();
    API api = retrofit.create(API.class);
    RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
    MultipartBody.Part part = MultipartBody.Part.createFormData(FILE, NAME, requestBody);
    Call<JsonResponse> call = api.uploadFile(NAME, FOLDER, part);

    call.enqueue(new Callback<JsonResponse>() {
        @Override
        public void onResponse(Call<JsonResponse> call, Response<JsonResponse> response) {
            //response.body()           null
            //response.code()           500

            //https://github.com/square/retrofit/issues/1321
            Converter<ResponseBody, JsonResponse> errorConverter
                    = retrofit.responseBodyConverter(JsonResponse.class, new Annotation[0]);
            try {
                JsonResponse jsonResponse = errorConverter.convert(response.errorBody());
                Log.e("error", "id:" + jsonResponse.id);                //1
                Log.e("error", "message:" + jsonResponse.message);      //An error has occurred
                Log.e("error", "path:" + jsonResponse.path);             //null
            } catch (IOException ignored) {
            }
        }

        @Override
        public void onFailure(Call<JsonResponse> call, Throwable t) {

        }
    });
}

Error in fields, because of any of these server issue

  1. Google
  2. HTTP 500 response using retrofit, but Post request through Postman gives desired response
  3. How to get response body to retrofit exception?
  4. Retrofit SocketTimeoutException (and/or http 500 error) on http-POST
Share:
29,182

Related videos on Youtube

ahmadalibaloch
Author by

ahmadalibaloch

Full Stack Developer, Focused on FrontEnd with 7+ years experience, core skills: Advanced Javascript/Typescript | 7 Years, React Latest | 4 years, Dashboards &amp; Data Visualizations | 3 years, Lead/Application architecturing | 2 years, Technology selection on Frontend | 2 years, BEM methodology, UX/UI | 4 years, Micro-Frontends &amp; Micro-services | 3 years. Other Technologies in vicinity and side projects: Python, Angular, Vue, Node, Blazor, .Net Core, Microservices Architecture, Docker, Data Visualizations, GIS, Kotlin/Android *Looking Forward to Relocate and make an impact in a bigger, diverse, multi-cultural community of passionate team members

Updated on July 14, 2022

Comments

  • ahmadalibaloch
    ahmadalibaloch almost 2 years

    Read edit at bottom of the question for possible alternative solution until the solution is found.

    This is a successful post file with two parameters using POSTMan. I am trying to do the same with retrofit but receive BadRequest.

    PostMan Settings:

    enter image description here

    Chrome Network Post Details: enter image description here

    Now here is how I am doing this in Android but failing:

    Retrofit Service Interface:

    @Multipart
    @POST("jobDocuments/upload")
    Call<ResponseBody> upload(@Part("file") MultipartBody.Part file,@Part("folder") MultipartBody.Part folder,@Part("name") MultipartBody.Part name);
    

    This is my @Background method to run the network request with above service generated

    CustDataClient service =
                ServiceGenerator.createService(CustDataClient.class);
        File file = new File(fileUri.getPath());
        // create RequestBody instance from file
        RequestBody requestFile =
                RequestBody.create(MediaType.parse("multipart/form-data"), file);
    
        MultipartBody.Part fileData =
                MultipartBody.Part.createFormData("file", fileName, requestFile);
        MultipartBody.Part folder =
                MultipartBody.Part.createFormData("folder", "LeadDocuments");
        MultipartBody.Part name =
                MultipartBody.Part.createFormData("name", fileName);
        // finally, execute the request
        Call<ResponseBody> call = service.upload(fileData,folder,name);
        try {
            Response<ResponseBody> rr = call.execute();
            ResponseBody empJobDocsResult = rr.body();//Bad Request here :(
            Log.v("Upload", "success");
        } catch (Exception ex) {
            Log.e("Upload error:", ex.getMessage());
        }
    

    Here is my Web Api Method:

     [Route("upload")]
        [HttpPost]
        public IHttpActionResult Upload()
        {
            if (HttpContext.Current.Request.Files.AllKeys.Any())
            {
                // Get the uploaded image from the Files collection
                var httpPostedFile = HttpContext.Current.Request.Files["file"];
    
                if (httpPostedFile != null)
                {
                    // Validate the uploaded image(optional)
                    var folder = HttpContext.Current.Request.Form["folder"];
                    var fileName = HttpContext.Current.Request.Form["name"];
                    fileName = string.IsNullOrEmpty(fileName) ? httpPostedFile.FileName : fileName;
                    // Get the complete file path
                    var fileSavePath = Path.Combine(HttpContext.Current.Server.MapPath("~/Files/" + folder), fileName);
    
                    // Save the uploaded file to "UploadedFiles" folder
                    httpPostedFile.SaveAs(fileSavePath);
    
                    return Ok(new OkMessage { Message = "File uploaded successfully", Path = "/Files/" + folder + "/" + fileName });
                }
            }
    
            return BadRequest("File not uploaded");
        }
    

    Please help where I am wrong and how to achieve this, is there any easy alternative to retrofit?

    [Edit] This code is working successfully, Thanks to koush/ion:

    Ion.with(getContext())
                                .load("POST", "http://www.dgheating.com/api/jobDocuments/upload")
                                .setMultipartParameter("folder", "LeadDocuments")
                                .setMultipartParameter("name", fileName)
                                .setMultipartFile("file", new File(imagePath))
                                .asJsonObject()
                                .setCallback(...);
    
    • Alberto M
      Alberto M almost 8 years
      nvm, I just saw your edit and Ion will be.

Related