Retrofit 2 can't upload a file with two additional separate string parameters
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
2) Correct POST
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
- HTTP 500 response using retrofit, but Post request through Postman gives desired response
- How to get response body to retrofit exception?
- Retrofit SocketTimeoutException (and/or http 500 error) on http-POST
Related videos on Youtube
ahmadalibaloch
Full Stack Developer, Focused on FrontEnd with 7+ years experience, core skills: Advanced Javascript/Typescript | 7 Years, React Latest | 4 years, Dashboards & Data Visualizations | 3 years, Lead/Application architecturing | 2 years, Technology selection on Frontend | 2 years, BEM methodology, UX/UI | 4 years, Micro-Frontends & 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, 2022Comments
-
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:
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 almost 8 yearsnvm, I just saw your edit and Ion will be.
-