Received Multipart File by Spring is null - android

I have a File and i want to send it to Spring Backend from Android. After receiving the image at Spring I am changing the name of the Image by Generating the UUID and then uploading it to AWS S3. My problem is i am getting null value as response.
Android Side ->
My Android Upload File Function ->
private void UploadFiles() {
File uploadFile = fileArrayList.get(0);
if (uploadFile != null) {
Log.d(TAG, "UploadFiles: File Name is -> " + uploadFile.getName());
// cropImageRequest.setCropId(uploadFile.getParentFile().getName());
// Parsing any Media type file
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), uploadFile);
// MultipartBody.Part is used to send also the actual file name
MultipartBody.Part cropImage = MultipartBody.Part.createFormData("cropImage", uploadFile.getName(), requestFile);
Api.uploadCropImage(cropImage, new Callback<BasicResponse>() {
#Override
public void onResponse(Call<BasicResponse> call, Response<BasicResponse> response) {
if (response.body() != null) {
Log.d(TAG, "onResponse: Success" + response.body().getResponse());
}
else{
Log.d(TAG, "onResponse: null Response");
}
}
#Override
public void onFailure(Call<BasicResponse> call, Throwable t) {
Log.d(TAG, "onResponse: Failure");
}
});
}
}
**uploadImageFunction -> **
public static void uploadCropImage(MultipartBody.Part cropImage, Callback<BasicResponse> callback) {
UploadCropImageApi uploadCropImageApi = retrofit.create(UploadCropImageApi.class);
Call<BasicResponse> call = uploadCropImageApi.uploadCropImage(cropImage);
call.enqueue(callback);
}
My Interface ->
public interface UploadCropImageApi {
#Multipart
#POST(UPLOAD_FILE_TO_AWS_URL)
Call<BasicResponse> uploadCropImage(#Part MultipartBody.Part cropImage);
}
This is my Spring File ->
#RequestMapping(value = "/UploadCropImage", method = RequestMethod.POST, consumes = {"multipart/form-data"})
#ResponseBody
public String UploadImage(#RequestBody MultipartFile cropImage,HttpServletRequest request) {
mAmazonClient = AmazonClient.GetAmazonClientInstance();
UUIDUtils uuid = new UUIDUtils();
try {
System.out.println(cropImage);
String KeyName = uuid.GenerateUUID(cropImage.getName());
String Code = mAmazonClient.uploadImage(KeyName, cropImage);
System.out.println(Code);
return Code;
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println(e.getMessage());
return null;
}
}
This Controller is printing following value ->
org.springframework.web.multipart.commons.CommonsMultipartFile#b0b5de0
File Name is -: cropImage
null
My Problem is that as you can see the file sent by Retrofit and received by Spring is not null, I am sending that file via AWS, but it's not uploading the file and returns null as value. But when i use POSTMAN it's easily sending the file to AWS and returns the KeyName.
Okay so i debug a little bit and found out an exception while uploading Image file. and the exception is this -> The filename, directory name, or volume label syntax is incorrect.

Your exception says there is something wrong in your file name. The problem is naming convention as it was uploading file name containing ":" sign and there may be other signs as well which is not accepted by AWS. so, just change file name and separate the names by _(Underscore) sign instead of " "(space), (:) and other signs. This will successfully upload the file to the AWS. Hope it helps.

Related

How to make multiple request and responses from all the requests in Retrofit, RxJava, Android

I have a scenario where I want to call same API for multiple devices and display result after completing all requests.
I am using retrofit 2.
I know little bit about RxJava. I thought zip operator will be suitable for this. So implemented as below.
API in ApiInterface :
#PUT(AppConstants.BASE_URL + AppConstants.PATH_SEPARATOR + "/user/endpoint")
Observable<ResponseBody> updateInfo(#Header("Authorization") String token, #Query("device_id") String deviceId, #Body JsonObject body);
Here is a method which calls API. It gets device id and its body in Map. This method calls API for every device id available in Map.
public void updateAllInfo(final HashMap<String, String> deviceIdMap, final ApiResponseListener listener) {
List<Observable<ResponseBody>> requests = new ArrayList<>();
ArrayList<String> reqIdList = new ArrayList<>();
for (Map.Entry<String, String> entry : map.entrySet()) {
String deviceId = entry.getKey();
String jsonBodyStr = entry.getValue();
Gson gson = new Gson();
JsonObject jsonBody = gson.fromJson(jsonBodyStr, JsonObject.class);
reqIdList.add(deviceId);
requests.add(apiInterface.updateSchedules("accessToken", deviceId, jsonBody));
}
Observable.zip(requests, new Function<Object[], List<ResponseBody>>() {
#Override
public List<ResponseBody> apply(Object[] objects) throws Exception {
Log.e("onSubscribe", "apply : " + objects.length);
List<ResponseBody> dataResponses = new ArrayList<>();
for (Object o : objects) {
dataResponses.add((ResponseBody) o);
}
return dataResponses;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<ResponseBody>>() {
#Override
public void accept(List<ResponseBody> responseBodies) throws Exception {
Log.e("onSubscribe", "YOUR DATA IS HERE: " + responseBodies.size());
for (int i = 0; i < responseBodies.size(); i++) {
Log.e(TAG, "Response received for " + i + " is : " + responseBodies.get(i).string());
}
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
Log.e("onSubscribe", "Throwable: " + throwable);
}
});
}
I want to get the response (success / failure) for every device id. Means I need response and also id for which API is called.
Using zip operator, if any API is failed, failure is received in accept(Throwable throwable) method. If any API is failed, I think zip operator is not calling next API.
How can I get response (success or failure) for all request ?
Also need something to indicate the response is for which req / device id (Some mapping) to display result.
Is there any other operator I can use instead of zip ?
Any suggestion / help ?
I am a bit rusty in java, so I will write my answer in Kotlin, it should not be a problem for you to convert it yourself.
Create a helper class that will include the ResponseBody alongside with the deviceId:
data class IdentifiedResponseBody(
val deviceId: String,
val responseBody: ResponseBody?
)
Then:
// change the signature of your requests list to return IdentifiedResponseBody observables
val requests = mutableListOf<Observable<IdentifiedResponseBody>>()
...
// your stated API have updateInfo instead of updateSchedules, but I will assume they have the same signature
requests.add(
apiInterface.updateSchedules("accessToken", deviceId, jsonBody)
.map { responseBody ->
// map the added observable to return IdentifiedResponseBody
IdentifiedResponseBody(deviceId, responseBody)
}
.onErrorReturn { error ->
// return an item here instead of throwing error, so that the other observables will still execute
IdentifiedResponseBody(deviceId, null)
}
)
Finally, use merge instead of zip:
Observable.merge(requests)
.subscribeOn(Schedulers.io())
// it's a question if you want to observe these on main thread, depends on context of your application
.subscribe(
{ identifiedResponse ->
// here you get both the deviceId and the responseBody
Log.d("RESPNOSE", "deviceId=${identifiedResponse.deviceId}, body=${identifiedResponse.responseBody}")
if (responseBody == null || responseBody.hasError()) {
// request for this deviceId failed, handle it
}
},
{ error ->
Log.e("onSubscribe", "Throwable: " + error)
}
)
See merge: http://reactivex.io/documentation/operators/merge.html
See zip: http://reactivex.io/documentation/operators/zip.html
You should see the profound difference: zip combines your responses to one single item defined by your mapping function (i.e. list of responses in your case), while merge emits all responses individually, at the time they are returned. In case of zip here, the combined result is returned at the moment (and only) when all the requests have finished; you may not want this behavior, as if a single request would not return a response, you would not get any response at all.
UPDATE
The java equivalent should be as follow, but revise before trying out, as I am not sure if I converted everything correctly:
requests.add(
apiInterface.updateSchedules("accessToken", deviceId, jsonBody)
.map(new Function<ResponseBody, IdentifiedResponseBody>() {
#Override
public IdentifiedResponseBody apply(ResponseBody responseBody) throws Exception {
return new IdentifiedResponseBody(deviceId, responseBody);
}
})
.onErrorReturn(new Function<Throwable, IdentifiedResponseBody>() {
#Override
public IdentifiedResponseBody apply(Throwable throwable) throws Exception {
return new IdentifiedResponseBody(deviceId, null);
}
})
);
Observable.merge(requests)
.subscribeOn(Schedulers.io())
.subscribe(new Consumer<IdentifiedResponseBody>() {
#Override
public void accept(IdentifiedResponseBody identifiedResponseBody) throws Exception {
// same logic as from kotlin part
}
},
new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
Log.e("onSubscribe", "Throwable: " + throwable);
}
});
UPDATE 2
In the comment you asked:
Is there any way from which I can get final callback for all requests
completed
That's the problem with using Observable instead of Single/Completable, it just does not finish unless you explicitly close the channel or an error is thrown. In ideal context, Observable should be used for streams that continuously emits some data, for example an open channel to Room DB, as there are no telling how many time the DB will change. I admit that in your case it seems to be difficult to apply something else than Observable. There is however a workaround:
Observable.merge(requests)
// emits only this much items, then close this channel
.take(requests.size.toLong())
// executed when the channel is closed or disposed
.doFinally {
// todo: finalCallback
}
.subscribeOn(Schedulers.io())
.subscribe(...)
The code is again in Kotlin, should not be hard to transform to java. Please check what Observable.take() does: http://reactivex.io/documentation/operators/take.html

I can't post with retrofit in real device but it works in emulator

I have an app that posting file with some string. I used retrofit 2. Everything is OK. but I have a problem. I run app in emulator an it works fine and goes to onResponse and post file and strings, but when I run app in real device it doesn't work and goes to onFailure in retrofit. What is the problem. I use galaxy s6 edge with android 7 API 24. and emulator is android 7.1.1 API 25. please help I should fix this problem :(
this is my retrofit code:
if (name.getText().toString().equals("") ||
description.getText().toString().equals("") ||
deadline.getText().toString().equals("")) {
popupForErrorEmptyField();
} else {
Client client = ServiceGenerator.createService(Client.class);
File file = null;
RequestBody requestFile = null;
MultipartBody.Part body = null;
if (userProfileImagePath != null) {
try {
file = new File(userProfileImagePath);
Uri uri = Uri.fromFile(file);
if (file.exists()) {
Log.i("finaltest", "found");
requestFile =
RequestBody.create(MediaType.parse(getMimeType(uri)), file);
body =
MultipartBody.Part.createFormData("projectFile", file.getName(), requestFile);
} else {
Log.i("finaltest", "not found");
}
} catch (Exception e) {
}
}
RequestBody nameRequestBody = RequestBody.create(MediaType.parse("text/plain"), name.getText().toString().trim());
RequestBody amountRequestBody = RequestBody.create(MediaType.parse("text/plain"), amount.getText().toString().trim());
RequestBody descriptionRequestBody = RequestBody.create(MediaType.parse("text/plain"), description.getText().toString().trim());
RequestBody categoryRequestBody = RequestBody.create(MediaType.parse("text/plain"), category.getSelectedItem().toString());
RequestBody deadlineRequestBody = RequestBody.create(MediaType.parse("text/plain"), deadline.getText().toString().trim());
Call<AddProjectResponse> call = client.addProject(
token,
body,
nameRequestBody,
amountRequestBody,
descriptionRequestBody,
categoryRequestBody,
deadlineRequestBody);
call.enqueue(new Callback<AddProjectResponse>() {
#Override
public void onResponse(Call<AddProjectResponse> call,
Response<AddProjectResponse> response) {
Log.e("xcxc", "code:" + response.code());
if (response.isSuccessful()) {
Log.e("xcxc", "success:");
popupForSuccessAddingProject();
} else {
Toast.makeText(getContext(), "successfull!", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<AddProjectResponse> call, Throwable t) {
Log.e("test123", "error in add project");
}
});
}
I am not sure this will solve everyone's problems, in my case it's one filed was expired which got the server run into crash, since it's working on the emulator and not on physical device do the following:
get Postman to use it in testing.
use the retrofit logger interceptor to collect the logs and get the all values of the request from both physical device and emulator
try the working request body on postman(in this case the emulator)
try to replace each field of the working request body(emulator) in postman with it's corresponding one from the not working request body (real device) and test the request at each change until one of them causes an error.
in my case the userId was expired and this cause an error from the server

Anonymous upload to Imgur's API using Retrofit 2

I let my user select a picture in their gallery, save its Uri, set a title and description and wish to upload it anonymously using Imgur's API, with this endpoint using Retrofit 2.
So far, this is what I am doing with no success:
In my ImgurAPI interface:
#Multipart
#POST("image")
Call<BrowseData> postImage(
#Header("Authorization") String auth,
#Part MultipartBody.Part file,
#Query("image") String image,
#Query("album") String albumId,
#Query("type") String type,
#Query("title") String title,
#Query("description") String description
);
In my API handler:
public void uploadImage(Uri fileUri, String image, String album, String type,
String title, String description) {
// create upload service client (retrofit builder and such)
ImgurAPI service =
ServiceGenerator.createService(ImgurAPI.class);
File file = FileUtils.getFile(caller.getActivity(), fileUri);
// create RequestBody instance from file
RequestBody requestFile =
RequestBody.create(
MediaType.parse(caller.getActivity().getContentResolver().getType(fileUri)),
file
);
// MultipartBody.Part is used to send also the actual file name
MultipartBody.Part body =
MultipartBody.Part.createFormData("picture", file.getName(), requestFile);
// finally, execute the request
Call<BrowseData> call = service.postImage(clientId, body, image, album, type, title, description);
call.enqueue(new Callback<BrowseData>() {
#Override
public void onResponse(Call<BrowseData> call,
Response<BrowseData> response) {
Log.v("Upload", "success");
}
#Override
public void onFailure(Call<BrowseData> call, Throwable t) {
Log.e("Upload error:", t.getMessage());
}
});
}
That is called like such on a simple FAB click:
controller = new ImgurAPIHandler(this);
controller.uploadImage(chosenUri, encodedImage, "AlbumName", "base64", title, desc);
But upon executing the request, I get the following error:
Write error: ssl=0x9c531200: I/O error during system call, Broken pipe
Can somebody explain to me what I am doing wrong? (If you have sufficient information).

Image uploading Android + Sails.js

What are the possible ways/libraries available to upload images from android app to sails.js Node.js server?
One way that I came across to achieve this is sending Base64 encoded Bitmap image string from the app and saving it to the database, but this seems to be an inefficient way to handle multiple big size images as Base64 encoded string is 33% larger than raw size.
Another way is to send images as multipart form data, but I couldn't found good examples on this. Please, provide examples which demonstrate how to send images from app and handle it at serverside (node.js/sails.js)
Are there any other recommended libraries available to handle image uploading in android?
I use Multer to handle file uploads via multipart form data.
Out of the box it can do memory and disk storage. By using plugin modules you can use Multer to send files direct to S3 or other storage providers.
For backend level, use this piece of code in your SailsJS application:
uploadPhoto: function (req, res) {
req.file('photo').upload({
adapter: require('skipper-s3'),
key: S3_KEY,
secret: S3_SECRET,
bucket: IMAGE_BUCKET_NAME,
dirname: DIRECTORY_NAME,
region: S3_REGION
}, function (err, uploaded) {
if(err) {
//Image not uploaded
//Returned with error
} else if(uploaded.length == 0) {
//Image not uploaded
} else {
//Image uploaded
//Returned Image Path
var imagePath = uploaded[0].extra.Location;
}
});
},
And you need to send the file using multipart request. I am doing it with retrofit library. Here is the android code:
1. Create Multipart RequestBody Object
RequestBody file =
RequestBody.create(MediaType.parse("multipart/form-data"), photo); //photo is of type "File"
2. Handling RequestBody in Interface
#Multipart
#POST("api/uploadphoto")
Call<ResponseBody> uploadPhoto(#Part("photo\"; filename=\"pp\"") RequestBody file);
3. Then initiating the call to server
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Response<ResponseBody> response, Retrofit retrofit) {
Log.e("RESPONSE", response.body());
}
}
#Override
public void onFailure(Throwable t) {
Log.e("UploadPhoto", "failed");
}
});
That will upload the image to S3-Bucket.
Sails.js Backend file upload code and file will be uploaded in assets/images folder
upload: function (req, res) {
if (req.method === 'GET')
return res.json({
'status': 'GET not allowed'
});
// Call to /upload via GET is error
var data = req.file('uploadFile');
data.upload({
dirname: '../../assets/images'
}, function onUploadComplete(err, files) {
// Earlier it was ./assets/images .. Changed to ../../assets/images
// Files will be uploaded to ./assets/images
// Access it via localhost:1337/images/file-name
console.log(files);
if (err) {
console.log(err);
return res.json(500, err);
} else if (files.length === 0) {
// proceed without files
res.notFound({
status: 'Unsucess',
response: 'File not Uploaded'
});
} else {
// handle uploaded file
res.json({
status: 200,
file: files
});
}
});
}
Android Code :-
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("fileUploadType", "1")
.addFormDataPart("miniType", contentType)
.addFormDataPart("ext", file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf(".")))
.addFormDataPart("fileTypeName", "img")
.addFormDataPart("clientFilePath", selectedImageUri.getPath())
.addFormDataPart("filedata", filename + ".png", fileBody)
.build();
Request request = new Request.Builder()
.url(API_URL)
.post(requestBody)
.build();
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, final IOException e) {
runOnUiThread(new Runnable() {
#Override
public void run() {
et_response.setText(e.getMessage());
Toast.makeText(MainActivity.this, "nah", Toast.LENGTH_SHORT).show();
}
});
}
#Override
public void onResponse(Call call, final Response response) throws IOException {
runOnUiThread(new Runnable() {
#Override
public void run() {
try {
et_response.setText(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
Toast.makeText(MainActivity.this, "response: " + response, Toast.LENGTH_LONG).show();
}
});
}
});
For Android Code You can Refer to
http://blog.aimanbaharum.com/2016/03/26/android-image-multi-part-upload/

How to receive file on Spring server send from a client using retrofit2

I am currently trying to upload a file from an android client using retrofit2 to a server using Spring Boot and its REST api.
CLIENT
I specifiy the upload method as described here: https://github.com/square/retrofit/issues/1063
public interface RetroRespondService {
#Multipart
#POST("/v1/answers")
public Call<ResponseDTO> sendPictures(#Part("file\"; filename=\"image.png")RequestBody image);
}
In another class the method to provide the actual image is declared:
(Now its just a test scenario. When image uploading is actually accomplished it will get more sophisticated.)
public void performAnswerRequest() {
try {
if (mRetrofit == null) {
mRetrofit = new Retrofit.Builder()
.baseUrl(DataHolder.getHostName())
.build();
}
//load test image
AssetManager manager = getAssets();
File file = new File(getFilesDir(), "image.png");
Utility.writeBytesToFile(new BufferedInputStream(manager.open("heart.png")), file);
RetroRespondService requestService = mRetrofit.create(RetroRespondService.class);
RequestBody image= RequestBody.create(MediaType.parse("multipart/form-data"), file);
Call<ResponseDTO> response = requestService.sendPictures(image);
response.enqueue(new AsyncAnswerResponse());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
SERVER
What I actually do not know is, how to properly get the image on the spring side.
#RequestMapping(value = API_VERSION + "/answers", method = RequestMethod.POST)
#ResponseBody
ResponseEntity<ResponseDTO> addAnswers(#RequestParam("file\"; filename=\"image.png") MultipartFile answers) throws DBEntryDoesNotExistException, EvaluationException, ParticipantException {
// In fact I have set a brake point here. Never entered the method yet, though
System.out.println("Yay!")
return null;
}
ERROR
Request: localhost:8080/v1/answers raised org.springframework.web.bind.MissingServletRequestParameterException:
Required MultipartFile parameter 'file"; filename="image.png' is not present
Since wireshark reports that in fact a request of size 1894 Bytes was send and this is the size of the image i want to upload I strongly believe the the data is actually transmitted but cannot be decoded from the server.
I have also seen this answers: How to config "CommonsMultipartResolver" in spring4 without xml to upload file
and subsequently implemented this class on the server side:
#Configuration
public class MultipartConfiguration {
#Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver resolver=new CommonsMultipartResolver();
resolver.setDefaultEncoding("utf-8");
resolver.setMaxUploadSize(1048576);
return resolver;
}
}
If you have any pointers in how to solve this I would appreciate your answer tremendously :)
If there are any questions left unanswered feel free to ask away.
Btw.: Sending and receiving JSON encoded data works just fine in both directions.

Categories

Resources