I'm trying to upload the file (heap dump) to slack channel using retrofi2 latest release version.
#Override
public void onCreate() {
super.onCreate();
slackApi = new Retrofit.Builder()
.baseUrl("https://slack.com/")
.build() //
.create(SlackApi.class);
}
#Multipart
#GTConverterAnnotation(value = GTConverterAnnotation.GSON)
#POST("api/files.upload")
Call<ResponseBody> uploadFile(
#Part("token") String token,
#Part("file") RequestBody file, #Part("filetype") String filetype,
#Part("filename") String filename, #Part("title") String title,
#Part("initial_comment") String initialComment, #Part("channels") String channels);
RequestBody file = RequestBody
.create(MediaType.parse("multipart/form-data"), heapDump.heapDumpFile);
final Call<ResponseBody> call = slackApi.uploadFile(SlackApi.TOKEN,
file,
null,
heapDump.heapDumpFile.getName(), title, initialComment,
SlackApi.MEMORY_LEAK_CHANNEL);
The following code fail with exception even before execution at "slack.uploaFile" with following exception:
> E/AndroidRuntime: FATAL EXCEPTION: IntentService[com.squareup.leakcanary.AbstractAnalysisResultService]
Process: com.gettaxi.dbx.android, PID: 11127
java.lang.IllegalArgumentException: Could not locate RequestBody converter for class java.lang.String.
Tried:
* retrofit2.BuiltInConverters
at retrofit2.Retrofit.nextRequestBodyConverter(Retrofit.java:298)
at retrofit2.Retrofit.requestBodyConverter(Retrofit.java:258)
at retrofit2.ServiceMethod$Builder.parseParameterAnnotation(ServiceMethod.java:577)
at retrofit2.ServiceMethod$Builder.parseParameter(ServiceMethod.java:328)
at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:201)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:166)
at retrofit2.Retrofit$1.invoke(Retrofit.java:145)
at java.lang.reflect.Proxy.invoke(Proxy.java:397)
at $Proxy13.uploadFile(Unknown Source)
at com.gettaxi.dbx.android.services.LeakSlackUploadService.afterDefaultHandling(LeakSlackUploadService.java:50)
at com.squareup.leakcanary.DisplayLeakService.onHeapAnalyzed(DisplayLeakService.java:86)
at com.squareup.leakcanary.AbstractAnalysisResultService.onHandleIntent(AbstractAnalysisResultService.java:49)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.os.HandlerThread.run(HandlerThread.java:61)
What am I missing? Why it's looking for RequestBody converter for the string?
update
Just created full solution similar to what Matrix suggest:
https://gist.github.com/parahall/cbba57d9d10f6dcd850f
First I want to note that there are other ways of achieving this but i went ahead just making sure the solution works with no major drawbacks.
Please check out my repo here:
Update: the url and repo Name has changed
https://github.com/MaTriXy/Slackrofit
Pasting relevant code as well:
#Multipart
#POST("api/files.upload")
Call<UploadFileResponse> uploadFile(
#Query("token") String token,
#PartMap Map<String, RequestBody> params,
#Query("filetype") String filetype,
#Query("filename") String filename, #Query("title") String title,
#Query("initial_comment") String initialComment, #Query("channels") String channels);
slackApi = new Retrofit.Builder().baseUrl("https://slack.com/").client(new OkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build().create(SlackApi.class);
String str = "Google Places API for Android Samples\n" +
"===================================\n" +
"\n" +
"Samples that use the [Google Places API for Android](https://developers.google.com/places/android/).\n" +
"\n" +
"This repo contains the following samples:";
file = RequestBody.create(MediaType.parse("multipart/form-data"), str.getBytes());
Map<String, RequestBody> map = new HashMap<>();
map.put("file\"; filename=\"heapDump.md\"", file);
call = slackApi.uploadFile(SlackApi.TOKEN, map, "text",
"heapDump.md", "Test Dump", "Check this out", SlackApi.MEMORY_LEAK_CHANNEL);
Later on to activate the call:
call.clone().enqueue(new Callback<SlackApi.UploadFileResponse>() {
#Override
public void onResponse(Call<SlackApi.UploadFileResponse> call, Response<SlackApi.UploadFileResponse> response) {
if (response != null) {
Log.e("GAG", response.body().toString());
}
}
#Override
public void onFailure(Call<SlackApi.UploadFileResponse> call, Throwable t) {
t.printStackTrace();
}
});
I'm using clone to test multiple uploads while this allows me not to rebuild a new call every time i want to use it.
UploadFileResponse is simple:
public static class UploadFileResponse {
boolean ok;
String error;
#Override
public String toString() {
return "UploadFileResponse{" +
"ok=" + ok +
", error='" + error + '\'' +
'}';
}
}
Have you add converter for your Retrofit?
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://api.example.com/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
Related
public void uploadFile(String token, String org, String fileName){
if(!ConnectionUtility.isNetworkAvailable2(getmContext())){
return;
}
retryTrigger = false;
File file = new File(fileName);
String fName = file.getName();
RequestBody fb = RequestBody.create(file, MediaType.parse("application/pdf"));
MultipartBody .Part body =
MultipartBody.Part.createFormData("uploadFile", fName, fb );
Map<String,String> headerMap = new HashMap<>();
headerMap.put("User-Agent", "KMP");
headerMap.put("X-Auth-Token", token);
headerMap.put("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundarytkRt9rKilDJeeFe1");
headerMap.put("accept", "application/json");
headerMap.put("Csrf-Token", "nocheck");
mCompositeDisposables.add(mKcpsLoginApiInterface.getFileUrl(org,headerMap,body)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retry(1, throwable -> {
retryTrigger = true;
return false;
})
.subscribeWith(new DisposableSingleObserver<Response<GetFileUrl>>() {
#Override
public void onSuccess(#NonNull Response<GetFileUrl> getFileUrlResponse) {
// Always get 400 error
}
#Override
public void onError(#NonNull Throwable e) {
}
})
);
}
My Interface looks like this
#Multipart
#POST("/o/{org}/files")
Single<Response<GetFileUrl>> getFileUrl(#Path("org") String org,
#HeaderMap Map<String,String> headers,
#Part MultipartBody.Part file
);
Additional Info:
I've used the correct token and headers using swagger site and it works but when somehow I code it on my project, it won't work. Insights would be much appreciated.
I always seem to get 400 error response code.
Thank you
So I have this POST request made to the server and based on an argument the server will return error message within the errorBody() of Retrofit. I am trying to handle that Plain Text error returned by the server and then display it to the user within my Android application which uses Java. Below is my current attempt but this is giving me this error in Logcat:
#Url cannot be used with #POST URL (parameter #1)
Here is 400 response from the server:
Interface:
public interface ChangePickLocationClient
{
#GET
Call<ResponseBody> checkItem(#Url String url, #Header("Authorization") String authToken);
#GET
Call<String> getStringError(#Url String url, #Header("Authorization") String authToken);
#POST("Pick/ChangePickLocationAcceptChange")
Call<String> changePickLocationPOST(#Url String url, #Header("Authorization") String authToken, #Body
ChangePickLocationPostModel changePickLocationPostModel);
}
Implementation:
private static final String BASE_URL = "http://00.00.00.1234/api/";
Gson mGson = new Gson();
Retrofit retrofit = new Retrofit.Builder().client(new OkHttpClient())
.baseUrl(BASE_URL).addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(mGson))
.build();
ChangePickLocationClient ChangePickLocationClient =
retrofitPOST.create(ChangePickLocationClient.class);
String itemNumber = itemNumberValue.getText().toString();
newPickLocationValue.setText(newPickLocationValue.getText().toString().toUpperCase());
String newPickLocation = newPickLocationValue.getText().toString();
String token = globalClass.getActiveToken();
final ChangePickLocationClient mChangePickLocationInterface =
retrofit.create(ChangePickLocationClient.class);
Call<String> mCallErrorPOST = mChangePickLocationInterface.changePickLocationPOST
(postUrl, "Bearer " + globalClass.getActiveToken(),
changePickLocationPostModel);
call.enqueue(new Callback<ChangePickLocationPostModel>()
{
#Override
public void onResponse(Call<ChangePickLocationPostModel> call,
Response<ChangePickLocationPostModel> response)
{
String mPlainTextResponse = null;
try {
if(response.errorBody() != null)
{
mPlainTextResponse = response.errorBody().string();
}
} catch (IOException e)
{
e.printStackTrace();
}
Toast.makeText(ChangePickLocation.this, mPlainTextResponse
,Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call<ChangePickLocationPostModel> call, Throwable t)
{
Toast.makeText(ChangePickLocation.this, "Unknown server error!"
,Toast.LENGTH_SHORT).show();
}
});
When the response is 400, the second call being made needs to be a clone() call. This is because the Call cannot be used more than once as stated in the documentation.
use this:
call.clone().enqueue(new Callback<ChangePickLocationPostModel>()
instead of
call.enqueue(new Callback<ChangePickLocationPostModel>()
I want to upload image on retrofit API with other 5 parameters, I tried all methods listed on stackoverflow but didn't succeed. I'm getting 500 Internal Server Error and {"message":"Unexpected token - in JSON at position 0"} this repsonse from server. I tried this in POSTMAN but API is working fine in it. Can you please tell me where I'm doing wrong.
Interface Code.
#Multipart
#Headers({"Content-Type: application/json;charset=UTF-8"})
#POST("uploaddoc")
Call<UploadDocuments> uploadDocuments(#PartMap Map<String, RequestBody> requestBodyMap,
#Part MultipartBody.Part file,
#Header("Authorization") String auth);
API call in activity.
private void uploadImage(String imagePath) {
File file = new File(imagePath);
RequestBody requestFile = RequestBody.create(file, MediaType.parse("multipart/form-data"));
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
Map<String, RequestBody> requestBodyMap = new HashMap<>();
requestBodyMap.put("label", RequestBody.create(encryptedLabel, MediaType.parse("multipart/form-data")));
requestBodyMap.put("role", RequestBody.create(encryptedRole, MediaType.parse("multipart/form-data")));
requestBodyMap.put("userobjid", RequestBody.create(encryptedUserObjId, MediaType.parse("multipart/form-data")));
requestBodyMap.put("whichtype", RequestBody.create(encryptedWhichType, MediaType.parse("multipart/form-data")));
requestBodyMap.put("gsttype", RequestBody.create(encryptedGstinSpinner, MediaType.parse("multipart/form-data")));
Call<UploadDocuments> documentsCall = equibiz_api_interface.uploadDocuments(requestBodyMap, body,"Bearer " + AuthToken);
documentsCall.enqueue(new Callback<UploadDocuments>() {
#Override
public void onResponse(#NotNull Call<UploadDocuments> call, #NotNull Response<UploadDocuments> response) {
UploadDocuments uploadDocuments1 = response.body();
assert uploadDocuments1 != null;
if(response.isSuccessful())
Toast.makeText(VerificationActivity.this, response.message(), Toast.LENGTH_SHORT).show();
else{
try {
assert response.errorBody() != null;
Toast.makeText(VerificationActivity.this, response.errorBody().string(), Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(#NotNull Call<UploadDocuments> call, #NotNull Throwable t) {
if (t instanceof SocketTimeoutException)
Toast.makeText(VerificationActivity.this, "Socket Time out. Please try again.", Toast.LENGTH_LONG).show();
else
Toast.makeText(VerificationActivity.this, t.toString(), Toast.LENGTH_LONG).show();
}
});
}
Postman Screenshot (Authorization token added into Header part)
I had gone through the Postman screenshot and your code. The code has some problems. Here are the problems and solutions.
The field name for the image/file is documentimages, not file. Change the field name like below.
RequestBody requestFile = RequestBody.create(file, MediaType.parse("multipart/form-data"));
MultipartBody.Part body = MultipartBody.Part.createFormData("documentimages", file.getName(), requestFile);
//^^^^^^ NOTICE THE CHANGES HERE
For text fields (the data you send along with file) set MediaTye as text/plain instead of multipart/form-data. For example,
requestBodyMap.put("gsttype", RequestBody.create(encryptedGstinSpinner, MediaType.parse("text/plain")));
Like the above shown, you need to change it to other text fields as well.
The headers you were using is not correct. If you use #Headers({"Content-Type: application/json;charset=UTF-8"}) as content type here, the Retrofit assumes that the data you send from client to server is JSON, not Multipart Form Data. If you get JSON as the response for the uploaded file from the server, then you can do it like below.
#Multipart
#Headers({"Accept: application/json;"}) // <===== CHANGE HEADER TYPE FROM Content-Tye to Accept
#POST("uploaddoc")
Call<UploadDocuments> uploadDocuments(#PartMap Map<String, RequestBody> requestBodyMap,
#Part MultipartBody.Part file,
#Header("Authorization") String auth);
Check the header part.
If you want to understand more about the headers, refer to this thread
I have tried all the methods available on the internet but nothing is working for me.
I'm trying to send images in a Single Key Value
{
files[]
}
This is my interface
public interface UserClient {
#Multipart
#POST(".")
Call<JsonElement> upload(
#Part("text") RequestBody text,
#Part("image") RequestBody image,
#Part("login") RequestBody login,
#Part List<MultipartBody.Part> files
);
}
this is my upload Method
public void uploadFiles(String text) {
QBUser user = SharedPrefsHelper.getInstance().getQbUser();
String userName = user.getFullName();
String image = "https://image.flaticon.com/icons/svg/146/146031.svg";
String URL = URLs.URL_POST + user.getPhone() + "/";
RequestBody textPart = RequestBody.create(MultipartBody.FORM, text);
RequestBody imagePart = RequestBody.create(MultipartBody.FORM, image);
RequestBody loginPart = RequestBody.create(MultipartBody.FORM, userName);
//list of files
List<MultipartBody.Part> filesList = new ArrayList<>();
for (int i = 0; i < arrayList_FilePath.size(); i++) {
filesList.add(prepareFilePart("files" + i, arrayList_FilePath.get(i)));
}
//Create retrofit instance
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(URL)
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
UserClient client = retrofit.create(UserClient.class);
Log.i(TAG, "uploadFiles: " + filesList);
Call<JsonElement> call = client.upload(textPart, imagePart, loginPart, filesList);
call.enqueue(new Callback<JsonElement>() {
#Override
public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {
finish();
Toast.makeText(getApplicationContext(), "Successful: " + response.message(),
Toast.LENGTH_LONG).show();
Log.i(TAG, "onResponse: " + response.body() + response.message());
}
#Override
public void onFailure(Call<JsonElement> call, Throwable t) {
Toast.makeText(getApplicationContext(), "failed", Toast.LENGTH_LONG).show();
}
});
}
This is preparefilePart Method
public MultipartBody.Part prepareFilePart(String partName, String path){
long imagename = System.currentTimeMillis();
File file = new File(path);
RequestBody requestBody = RequestBody.create(
MediaType.parse("image/*"),
file
);
return MultipartBody.Part.createFormData(partName, imagename + ".jpeg" ,requestBody);
}
If I send only text request without images its working fine but when i send image it's not working
it's giving an error Code 500 Internal Server Error with null response in body
Note: Api is working Great in Postman
file was not uploading because file was not Creating Properly
thanks to github i found a way to create a file Properly
GitHub link for creating file
Also Made a little bit of changes in my code according to this answer
link for stackoverflow answer
I am trying to upload video to server using Retrofit as multi part form data , And I am using Retrofit version: 2.02
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
This is the code to the API client class:
ApiClient.java
public class ApiClient {
public static final String BASE_URL = "https://api.sproutvideo.com";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
This is the code to API interface:
ApiInterface.java
public interface ApiInterface {
#Multipart
#POST("/v1/videos")
Call<SproutReply> pushVideo (#Header("SproutVideo-Api-Key") String key, #Part("file\"; filename=\"pp.mp4") RequestBody file);
}
And my requirement is to upload the video picked using Intent.ACTION_PICK.
And this is how I invoke the API:
private void makeRequest(Uri uri)
{
try {
String path = uri.toString();
Log.e("PATH", path);
URI ss = new URI(uri.toString());
file = new File(ss);
}
catch (Exception e){}
RequestBody video = RequestBody.create(MediaType.parse("video/mp4"),file);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<SproutReply> call = apiService.pushVideo(KEY,video);
call.enqueue(new Callback<SproutReply>()
{
#Override
public void onResponse(Call<SproutReply> call, Response<SproutReply> response)
{
try
{
Log.e("TAG",""+response.body().toString());
}
catch (Exception e)
{
Toast.makeText(getActivity(), "Check data connection", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<SproutReply> call, Throwable t)
{
// Log error here since request failed
Log.e("FAILURE", t.toString());
}
});
}
The call results in a NullPointerException as content == null at this line:
RequestBody video = RequestBody.create(MediaType.parse("video/mp4"),file);
However in this line when I log the Uri( Log.e("PATH", path); ) as String I obtain the value as :
content://com.android.providers.media.documents/document/video%3A75406
And using this Uri I am able to play video in VideoView as well, however in Retrofit it seems to crash, what is possibly causing this and how to fix?
Here is the logcat as well:
java.lang.NullPointerException: content == null
at okhttp3.RequestBody.create(RequestBody.java:103)
at com.example.demovid.UploadFragment.makeRequest(UploadFragment.java:91)
at com.example.demovid.UploadFragment.onEvent(UploadFragment.java:133)
at java.lang.reflect.Method.invoke(Native Method)
at org.greenrobot.eventbus.EventBus.invokeSubscriber(EventBus.java:485)
at org.greenrobot.eventbus.EventBus.postToSubscription(EventBus.java:416)
at org.greenrobot.eventbus.EventBus.postSingleEventForEventType(EventBus.java:397)
at org.greenrobot.eventbus.EventBus.postSingleEvent(EventBus.java:370)
at org.greenrobot.eventbus.EventBus.post(EventBus.java:251)
at com.example.demovid.MainActivity.onActivityResult(MainActivity.java:67)
at android.app.Activity.dispatchActivityResult(Activity.java:6456)
at android.app.ActivityThread.deliverResults(ActivityThread.java:3729)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:3776)
at android.app.ActivityThread.-wrap16(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1412)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5461)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
The issue is that you have a content URI and not a file path. When you pass that to the File constructor, it throws a FileNotFoundException that you silently ignore with
`catch (Exception e){}`
Instead of using a file, you can get a file descriptor to the data using --
ParcelFileDescriptor fd = getActivity().getContentResolver().openFileDescriptor(uri, "r");
and use that to construct your RequestBody. There is no out of the box method to create one from the descriptor, but it is fairly easy to write a routine to do it --
public static RequestBody createBody(final MediaType contentType, final ParcelFileDescriptor fd) {
if (fd == null) throw new NullPointerException("content == null");
return new RequestBody() {
#Override public MediaType contentType() {
return contentType;
}
#Override public long contentLength() {
return fd.getStatSize();
}
#Override public void writeTo(BufferedSink sink) throws IOException {
Source source = null;
try {
source = Okio.source(new ParcelFileDescriptor.AutoCloseInputStream(fd));
sink.writeAll(source);
} finally {
Util.closeQuietly(source);
}
}
};
}
then use it like --
RequestBody video = RequestBody.createBody(MediaType.parse("video/mp4"), fd);
I also noticed that you are hardcoding the media type, you can get the type from the content resolver as well with a call to getType