Send a name/value pair as object in Multipart using Retrofit - android

In my code I have to make a MultipartForm-Data PUT request to update an object in the server, it ought to be Multipart as it is possible the user will send an image together with the data.
In order to do that, I am currently using Retrofit since it's a library I'm decently used to and it's working to send images to the server.
However, things have changed server-side and now one of the parameters that must be sent is:
{"step":
{"type":"begin"}
}
However that's been proving to be surprisingly hard to do.
Things I have tried include passing it as a MultipartTypedOutput, a hand-typed String and a JSONObject converted to String, all of which gave me:
retrofit.RetrofitError: 400 Bad Request
The URL being used is correct, I've double checked with the person who maintains the server and it is reaching the server, but with an incorrect "step" object.
I've also tried passing it as NameValuePair, Map and HashMap, all of which gave me:
retrofit.RetrofitError: Part body must not be null.
#FieldPart which looks to be perfect for this isn't compatible with Multipart, so is there a way to do this with Retrofit at all?
My current PUT method is as such:
#Headers({
"Connection: Keep-Alive",
"Accept-Language: en-US"
})
#Multipart
#PUT("/0.1/user/{id}")
String updateUser(#Path("id") String userId, #Part("step") Map<String,String> type);
Where the Map type has been changed to all the types I mentioned before.

You are actually doing it the correct way,just need some quick fix. I have two suggestions for you,
1. You can create a innerclass like this
public class Example {
#SerializedName("type")
#Expose
private String type;
/**
*
* #return
* The type
*/
public String getType() {
return type;
}
/**
*
* #param type
* The type
*/
public void setType(String type) {
this.type = type;
}
}
In this case, your api will look like
#Multipart
#PUT("/0.1/user/{id}")
String updateUser(#Path("id") String userId, #Part("step") Example exp);
or, you can use the JsonObject, this is not the Apache JSONObject that you have already used. Its the google gson object which comes under package of com.google.gson. Here you need to do the following,
JsonObject settingObject = new JsonObject();
settingObject.addProperty("type", "begin");
In this case, it will be,
String updateUser(#Path("id") String userId, #Part("step") JsonObject obj);
This is all you need, btw you can also set the header for once and all, why bother defining it over an api ?

I still don't understand what the issue is, as the "Part body must not be null" occurred even when trying out what Ankush mentioned.
Either way, I spoke to a few friends and a few contact expansions later, I got the following solution:
#Headers({
"Connection: Keep-Alive",
"Accept-Language: en-US"
})
#Multipart
#PUT("/0.1/user/{id}")
String updateUser(#Path("id") String userId, #Part("step[type]") String type);
As far as I could find, this isn't mentioned anywhere in retrofit's documentation, but it does work.

Related

Does Retrofit support passing JSON as parameter, yet call it as normal parameters?

Background
On some API calls to the server, instead of the normal parameters to the interface, like this:
interface SomeInterface {
#POST("first_launch") fun sendFirstLaunch(
#Path("referral_code") referralCode: String?,
#Path("referral_source") referralSource: String?): Call<BaseDCResponse>
}
We actually need to send those parameters as a JSON in the body.
The problem
I'm not an expert in Retrofit, but according to what I've found (here for example), I can only pass a Json String to the interface, meaning:
interface SomeInterface {
#POST("first_launch") fun sendFirstLaunch(#Body jsonString: String): Call<BaseDCResponse>
}
According to here, I believe I can also send a serialized object, instead. Meaning something like:
interface SomeInterface {
class SendFirstLaunchRequest(#Path("referral_code") val referralCode: String?,
#Path("referral_source") val referralSource: String?)
#POST("first_launch") fun sendFirstLaunch(
#Body body: SendFirstLaunchRequest): Call<BaseDCResponse>
}
This loses the nice way to reach the function, while making me add the Json data manually for each function I put on the interface (or create new classes to pass there). I want to avoid this, and have something similar to the original.
What I've tried
I tried to search more and more about this, but it doesn't seem like this was requested.
Maybe I saw the answers but didn't understand them.
I think even the official website shows some clues about this:
https://square.github.io/retrofit/
Seeing that I don't think it's possible, I've also added a request for it here.
The questions
Does Retrofit allow to send the parameters I set to the function, to be a Json data as a body?
If not, is there any nice workaround for this? Am I correct that I could only pass a serialized object instead? If so, what's the proper way to do it? Maybe like here?
Using Retrofit2:
I came across this problem last night migrating from Volley to Retrofit2 (and as OP states, this was built right into Volley with JsonObjectRequest), and although Jake's answer is the correct one for Retrofit1.9, Retrofit2 doesn't have TypedString.
My case required sending a Map<String,Object> that could contain some null values, converted to a JSONObject (that won't fly with #FieldMap, neither does special chars, some get converted), so following #bnorms hint, and as stated by Square:
An object can be specified for use as an HTTP request body with the #Body annotation.
The object will also be converted using a converter specified on the Retrofit instance. If no converter is added, only RequestBody can be used.
So this is an option using RequestBody and ResponseBody:
In your interface use #Body with RequestBody
public interface ServiceApi
{
#POST("Your api end point")
Call<ResponseBody> login(#Header("X_API_KEY") String header, #Body RequestBody body);
}
In your calling point create a RequestBody, stating it's MediaType, and using JSONObject to convert your Map to the proper format:
Map<String, Object> jsonParams = new ArrayMap<>();
//put something inside the map, could be null
jsonParams.put("name", some_code);
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),(new JSONObject(jsonParams)).toString());
//serviceCaller is the interface initialized with retrofit.create...
Call<ResponseBody> response = serviceCaller.login(header, body);
response.enqueue(new Callback<ResponseBody>()
{
#Override
public void onResponse(Call<ResponseBody> call,retrofit2.Response<ResponseBody> rawResponse)
{
try
{
//get your response....
Log.d(TAG, "RetroFit2.0 :RetroGetLogin: " + rawResponse.body().string());
}
catch (Exception e)
{
e.printStackTrace();
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable throwable)
{
// other stuff...
}
});
Hope this Helps anyone!
Seems it doesn't, and that there is a request to offer something to help handling with this:
https://github.com/square/retrofit/issues/2890

retrofit2 errorbody.content is not accessible

I am new in retrofit2 world, currently I have a problem on getting errorbody from onNext.
Here is my sample code :
public void onNext(Response<LoginResponse> value) {
ResponseBody responseBody = value.errorBody();
String sam = responseBody.toString();
}
My issue is, I cant get the errorbody().content. It is like it is not accessible.
You can try:
String errorBody = value.errorBody().string;
or
String errorBody = value.errorBody().toString();
Retrofit's error bodys are of type OkHttp3 ResponseBody. This class is abstract and its implementations define different ways the content is represented internally.
There are different ways you can get the content. You can get it in bytes, as a string, or even get an InputStream for it - check them out here.
I suppose an easy way is to use string():
value.errorBody().string();
Note that these methods usually read the response from a buffer, which means that if I recall correctly, calling again string() would not give you the content of the response again. In particular, string() also reads the entire body into memory, which may cause an out of memory exception.
After you have the content, if you want it as an object from your data model, you'll have to deserialize it manually. There are numerous ways to do this and it's easy to find on the web, but for sake of completeness, here's a way to do it with gson:
String error = value.errorBody().string();
MyPojo pojo = new Gson().fromJson(error, MyPojo.class);
(here MyPojo would be your error class representing the error)
You can't get errorBody without http-error code.
Use smt like this:
if (!value.isSuccessful && value.errorBody() != null) {
val errorBodyText = value.errorBody()?.string()?:""
val errorResponse = Gson().fromJson<ErrorObject>(errorBodyText, ErrorObject::class.java)
}

Retrofit2 - Percentage progressbar for request with long Base64 String in parameter

I have question... I want convert photo taken from device to Base64 String, insert to request body and post it on server via Retrofit 2. There is example of request body:
class MyRequestBody {
private String name;
private String description;
private String photoBase64;
...
}
And there is example of API endpoint (Single<> is from RxJava, eventually I can use Observable<>):
#POST("upload")
Single<ApiResponse> uploadPhoto(#Body MyRequestBody myRequestBody);
My question is, if it's possible show in progressbar (in percent) how much data was already sent? And is this good solution or is better upload photo as file?
I want use on it Retrofit2, RxJava2 and RxAndroid.

android Expected BEGIN_OBJECT but was STRING at line 1 column 1 [duplicate]

I have this method:
public static Object parseStringToObject(String json) {
String Object = json;
Gson gson = new Gson();
Object objects = gson.fromJson(object, Object.class);
parseConfigFromObjectToString(object);
return objects;
}
And I want to parse a JSON with:
public static void addObject(String IP, Object addObject) {
try {
String json = sendPostRequest("http://" + IP + ":3000/config/add_Object", ConfigJSONParser.parseConfigFromObjectToString(addObject));
addObject = ConfigJSONParser.parseStringToObject(json);
} catch (Exception ex) {
ex.printStackTrace();
}
}
But I get an error message:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException:
Expected BEGIN_OBJECT but was STRING at line 1 column 1
Even without seeing your JSON string you can tell from the error message that it is not the correct structure to be parsed into an instance of your class.
Gson is expecting your JSON string to begin with an object opening brace. e.g.
{
But the string you have passed to it starts with an open quotes
"
Invalid JSON from the server should always be an expected use case. A million things can go wrong during transmission. Gson is a bit tricky, because its error output will give you one problem, and the actual exception you catch will be of a different type.
With all that in mind, the proper fix on the client side is
try
{
gson.fromJSON(ad, Ad.class);
//...
}
catch (IllegalStateException | JsonSyntaxException exception)
{
//...
If you want to know why the JSON you received from the server is wrong, you can look inside your catch block at the exception. But even if it is your problem, it's not the client's responsibility to fix JSON it is receiving from the internet.
Either way, it is the client's responsibility to decide what to do when it gets bad JSON. Two possibilities are rejecting the JSON and doing nothing, and trying again.
If you are going to try again, I highly recommend setting a flag inside the try / catch block and then responding to that flag outside the try / catch block. Nested try / catch is likely how Gson got us into this mess with our stack trace and exceptions not matching up.
In other words, even though I'll admit it doesn't look very elegant, I would recommend
boolean failed = false;
try
{
gson.fromJSON(ad, Ad.class);
//...
}
catch (IllegalStateException | JsonSyntaxException exception)
{
failed = true;
//...
}
if (failed)
{
//...
I had a similar problem recently and found an interesting solution. Basically I needed to deserialize following nested JSON String into my POJO:
"{\"restaurant\":{\"id\":\"abc-012\",\"name\":\"good restaurant\",\"foodType\":\"American\",\"phoneNumber\":\"123-456-7890\",\"currency\":\"USD\",\"website\":\"website.com\",\"location\":{\"address\":{\"street\":\" Good Street\",\"city\":\"Good City\",\"state\":\"CA\",\"country\":\"USA\",\"postalCode\":\"12345\"},\"coordinates\":{\"latitude\":\"00.7904692\",\"longitude\":\"-000.4047208\"}},\"restaurantUser\":{\"firstName\":\"test\",\"lastName\":\"test\",\"email\":\"test#test.com\",\"title\":\"server\",\"phone\":\"0000000000\"}}}"
I ended up using regex to remove the open quotes from beginning and the end of JSON and then used apache.commons unescapeJava() method to unescape it. Basically passed the unclean JSON into following method to get back a cleansed one:
private String removeQuotesAndUnescape(String uncleanJson) {
String noQuotes = uncleanJson.replaceAll("^\"|\"$", "");
return StringEscapeUtils.unescapeJava(noQuotes);
}
then used Google GSON to parse it into my own Object:
MyObject myObject = new.Gson().fromJson(this.removeQuotesAndUnescape(uncleanJson));
In Retrofit2, When you want to send your parameters in raw you must use Scalars.
first add this in your gradle:
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:converter-scalars:2.3.0'
public interface ApiInterface {
String URL_BASE = "http://10.157.102.22/rest/";
#Headers("Content-Type: application/json")
#POST("login")
Call<User> getUser(#Body String body);
}
my SampleActivity :
public class SampleActivity extends AppCompatActivity implements Callback<User> {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ApiInterface.URL_BASE)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiInterface apiInterface = retrofit.create(ApiInterface.class);
// prepare call in Retrofit 2.0
try {
JSONObject paramObject = new JSONObject();
paramObject.put("email", "sample#gmail.com");
paramObject.put("pass", "4384984938943");
Call<User> userCall = apiInterface.getUser(paramObject.toString());
userCall.enqueue(this);
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public void onResponse(Call<User> call, Response<User> response) {
}
#Override
public void onFailure(Call<User> call, Throwable t) {
}
}
Reference: [How to POST raw whole JSON in the body of a Retrofit request?
I have come to share an solution. The error happened to me after forcing the notbook to hang up. possible solution clean preject.
Maybe your JSON Object is right,but the response that you received is not your valid data.Just like when you connect the invalid WiFi,you may received a strange response < html>.....< /html> that GSON can not parse.
you may need to do some try..catch.. for this strange response to avoid crash.
Make sure you have DESERIALIZED objects like DATE/DATETIME etc. If you are directly sending JSON without deserializing it then it can cause this problem.
In my situation, I have a "model", consist of several String parameters, with the exception of one: it is byte array byte[].
Some code snippet:
String response = args[0].toString();
Gson gson = new Gson();
BaseModel responseModel = gson.fromJson(response, BaseModel.class);
The last line above is when the
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column
is triggered. Searching through the SO, I realised I need to have some form of Adapter to convert my BaseModel to and fro a JsonObject. Having mixed of String and byte[] in a model does complicate thing. Apparently, Gson don't really like the situation.
I end up making an Adapter to ensure byte[] is converted to Base64 format. Here is my Adapter class:
public class ByteArrayToBase64Adapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
#Override
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return Base64.decode(json.getAsString(), Base64.NO_WRAP);
}
#Override
public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(Base64.encodeToString(src, Base64.NO_WRAP));
}
}
To convert JSONObject to model, I used the following:
Gson customGson = new GsonBuilder().registerTypeHierarchyAdapter(byte[].class, new ByteArrayToBase64Adapter()).create();
BaseModel responseModel = customGson.fromJson(response, BaseModel.class);
Similarly, to convert the model to JSONObject, I used the following:
Gson customGson = new GsonBuilder().registerTypeHierarchyAdapter(byte[].class, new ByteArrayToBase64Adapter()).create();
String responseJSon = customGson.toJson(response);
What the code is doing is basically to push the intended class/object (in this case, byte[] class) through the Adapter whenever it is encountered during the convertion to/fro JSONObject.
Don't use jsonObject.toString on a JSON object.
In my case, I am Returning JSON Object as
{"data":"","message":"Attendance Saved
Successfully..!!!","status":"success"}
Resolved by changing it as
{"data":{},"message":"Attendance Saved
Successfully..!!!","status":"success"}
Here data is a sub JsonObject and it should starts from { not ""
Don't forget to convert your object into Json first using Gson()
val fromUserJson = Gson().toJson(notificationRequest.fromUser)
Then you can easily convert it back into an object using this awesome library
val fromUser = Gson().fromJson(fromUserJson, User::class.java)
if your json format and variables are okay then check your database queries...even if data is saved in db correctly the actual problem might be in there...recheck your queries and try again.. Hope it helps
I had a case where I read from a handwritten json file. The json is perfect. However, this error occurred. So I write from a java object to json file, then read from that json file. things are fine. I could not see any difference between the handwritten json and the one from java object. Tried beyondCompare it sees no difference.
I finally noticed the two file sizes are slightly different, and I used winHex tool and detected extra stuff.
So the solution for my situation is, make copy of the good json file, paste content into it and use.
In my case, my custom http-client didn't support the gzip encoding. I was sending the "Accept-Encoding: gzip" header, and so the response was sent back as a gzip string and couldn't be decoded.
The solution was to not send that header.
I was making a POST request with some parameters using Retrofit in Android
WHAT I FACED:
The error I was getting in Android Studio logcat:
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING
at line 2 column 1 path $
[but it was working fine with VOLLY library]
when I googled it...
you know[ Obviously json is expecting a OBJECT but...]
BUT when I changed my service to return a simple string [ like print_r("don't lose hope") ] or
Noting at all
It was getting printed fine in Postman
but in Android studio logcat, it was still SAME ERROR [
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING
at line 2 column 1 path $
]
Hold up now, I am sending a simple message or not sending anything in response and still studio is
telling me "...Expected BEGIN_OBJECT but was STRING..."
SOMETHING IS WRONG
On 4th day:
I finally stopped for looking "QUICK SOLUTIONS" and REALLY READ some stack overflow questions
and articles carefully.
WHAT I GOT:
Logging interceptor
It will show you whatever data comes from your server[even eco messages] which are not shown in
Andorid studios logcat,
that way you can FIND THE PROBLEM.
What I found is I was sending data with #Body like-
#Headers("Content-Type: application/json")
#POST("CreateNewPost")
Call<Resp> createNewPost(#Body ParaModel paraModel);
but no parameter was reaching to server, everything was null [I found using Logging interceptor]
then I simply searched an article "how to make POST request using Retrofit"
here's one
SOLUTION:
from here I changed my method to:
#POST("CreateNewPost")
#FormUrlEncoded
Call<Resp> createNewPost(
#Field("user_id") Integer user_id,
#Field("user_name") String user_name,
#Field("description") String description,
#Field("tags") String tags);
and everything was fine.
CONCLUSION:
I don't understand why Retrofit gave this error
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING
at line 2 column 1 path $
it doesn't make any sense at all.
So ALWAYS DEBUG in detail then find WHERE THINGS ARE LEAKING and then FIX.
This error solved for by replacing .toString method to .string on the response
toString => string (add in try{...code..}catche(IOException e))
below code is working for me
try {
MainModelResponse model;
Gson gson = new GsonBuilder().create();
if (response.code() == ConstantValues.SUCCESS_OK) {
model = gson.fromJson(response.body().string(), MainModelResponse.class);
} else {
model = gson.fromJson(response.errorBody().string(), MainModelResponse.class);
}
moduleData.postValue(model);
}catch (IllegalStateException | JsonSyntaxException | IOException exception){
exception.printStackTrace();
}
}
use a string begin & end with {}.
such as
final String jsStr = "{\"metric\":\"opentsdb_metric\",\"tags\":{\"testtag\":\"sunbotest\"},\"aggregateTags\":[],\"dps\":{\"1483399261\":18}}";
DataPoint dataPoint = new Gson().fromJson(jsStr, DataPoint.class);
this works for me.
In my case the object was all fine even the Json Validator was giving it a valid resposne but I was using Interface like this
#POST(NetworkConstants.REGISTER_USER)
Call<UserResponse> registerUser(
#Query("name") String name,
#Query("email") String email,
#Query("password") String password,
#Query("created_date") Long creationDate
);
Then I changed the code to
#FormUrlEncoded
#POST(NetworkConstants.REGISTER_USER)
Call<UserResponse> registerUser(
#Field("name") String name,
#Field("email") String email,
#Field("password") String password,
#Field("created_date") Long creationDate
);
And everything was resolved.
my problem not related to my codes
after copy some files from an other project got this issue
in the stack pointed to Gson library
in android studio 4.2.1 this problem not solved when I try file-> invalidate and restart
and
after restart in first time build got same error but in second build this problem solved
I don't understand why this happened
I was using an old version of retrofit library. So what I had to do was to change my code from this after upgrading it to com.squareup.retrofit2:retrofit:2.9.0:
#POST(AppConstants.UPLOAD_TRANSACTION_DETAIL)
fun postPremiumAppTransactionDetail(
#Query("name") planName:String,
#Query("amount") amount:String,
#Query("user_id") userId: String,
#Query("sub_id") planId: String,
#Query("folder") description:String,
#Query("payment_type") paymentType:String):
Call<TransactionResponseModel>
To this:
#FormUrlEncoded
#POST(AppConstants.UPLOAD_TRANSACTION_DETAIL)
fun postPremiumAppTransactionDetail(
#Field("name") planName:String,
#Field("amount") amount:String,
#Field("user_id") userId: String,
#Field("sub_id") planId: String,
#Field("folder") description:String,
#Field("payment_type") paymentType:String):
Call<TransactionResponseModel>
For me it turned out that I was trying to deserialize to an object that used java.time.ZonedDateTime for one of the properties. It worked as soon as I changed it to a java.util.Date instead.

Retrofit2 Static encoded form data?

I have a new Android project I am working on, and Retrofit2 is working well for me. However, I have one url that I need to hit, with one of two strings, on top of the data I send.
Right now it looks like this:
#FormUrlEncoded
#POST("token")
Call<AccessResponse> requestAccess(#Field("grant_type") String type, #Field("code") String authCode, #Field("client_id") String ApiKey);
the grant type is only one of two things, and I would like to abstract it away, into static urls, like this:
#FormUrlEncoded
#POST("token")
#Field("grant_type","type1")
Call<AccessResponse> requestAccess( #Field("code") String authCode, #Field("client_id") String ApiKey);
#FormUrlEncoded
#POST("token")
#Field("grant_type","type2")
Call<AccessResponse> refreshAccess( #Field("code") String authCode, #Field("client_id") String ApiKey);
Is there a way to accomplish this? my 2 days of google-fu haven't worked for me, nor has browsing the API docs and code. I just don't want to have to keep track of the correct string in the various places in my code.
Turns out the answer is "You can't right now".
There is an open issue on Github for the feature request
There is an alternative approach, a FieldMap object with a method example as mentioned in this SO post, but it is not exactly what I was looking for, and overkill for just one field.
Could the Retrofit RequestInterceptor do the job ?
It can inject parameters into each request... so from there you could maybe write a method that injects the right parameter depending on what you're trying to do...
RequestInterceptor requestInterceptor = new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
request.addQueryParam("grant_type", getGrantType());
}
};
private String getGrantType()
{
// do your stuff and :
return "type1"; // or "type2"
}

Categories

Resources