Retrofit2 Static encoded form data? - android

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"
}

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

Using retrofit to get url from different relative paths

I am trying to get the CompanyEndpoint for each client's site but I am confused with the use of retrofit on the interface.
Here's what I have so far:
CompanyName : "company1"
CompanyEndpoint : "https://example.com"
IdentityEndpoint : "https://example.com/identity"
AppLoginMode : "Anonymous"
AppRouterApi.java
public interface AppRouterApi {
#GET("api/sites/{CompanyName}")
Call<Company> getCompanyName (#Url String companyName);
}
Company.java
public class Company {
String Endpoint;
public String getEndpoint() {
return endpoint;
}
}
MainActivity.java
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
appRouterApi = retrofit.create(AppRouterApi.class);
getCompany();
}
private void getCompany(){
retrofit2.Call<Company> companyRequest = appRouterApi.getCompanyName(); //Error here saying a string cant be applied to ()
companyRequest.enqueue(new retrofit2.Callback<Company>() {
#Override
public void onResponse(retrofit2.Call<Company> call, retrofit2.Response<Company> response) {
if(!response.isSuccessful()){
textViewResult.setText("Code:" + response.code());
return;
}
Company company = response.body();
String content = "";
content += "Url" + company.getEndpoint();
textViewResult.setText(content);
}
#Override
public void onFailure(retrofit2.Call<Company> call, Throwable t) {
}
});
}
https://example/sites/{companyName}
So if I search for:
https://example/sites/company1
The JSON will have one object and I need to get the endpoint URL value which would be: https://company1.com
Edit: My textViewReslt is returning 403
There are several things going on as far as I can tell. Let me break it into chunks.
First thing is you're confusing the annotation #Path with the annotation #Url. They serve different purposes.
You use #Path when you want to format a bit of the path into the url inside the annotations like #GET.
public interface AppRouterApi {
#GET("api/sites/{CompanyName}")
Call<Company> getCompanyName (#Path("CompanyName") String companyName);
}
This interface will format the argument passed to getCompanyName as part of the path. Calling getCompanyName("foo") will call the endpoint "https://example.com/api/sites/foo".
You use #Url when you want to simply call that url. In this case, you only annotate the interface method with the http method. For example,
public interface AppRouterApi {
#GET
Call<Company> getCompanyName (#Url String url);
}
You then would have to call the method with the entire url. To call the same url as before you'd have to call getCompanyName("https://example.com/api/sites/foo").
This is the main difference of usage between these 2 annotations. The reason why you're seeing null in your text view is because you're model's attribute name doesn't match the json. You have 2 options.
First, you can change the model to:
public class Company {
String CompanyEndpoint;
public String getEndpoint() {
return endpoint;
}
}
CompanyEndpoint is the exact same name as you have in the json. Another approach, is to tell your json serializer what name you want to use. Since you're using gson, you can use #SerializedName like so:
public class Company {
#SerializedName("CompanyEndpoint")
String Endpoint;
public String getEndpoint() {
return endpoint;
}
}
#SerializedName("CompanyEndpoint") tells gson which name to use while serializing and deserializing.
In essence, you have 2 options. You either use the endpoint, or the company's name. If you don't expect the domain to change, I'd suggest using the first approach with the #Path annotation. This is what it's usually done with Retrofit and personally, I think it's easier to handle than passing urls around. My suggestion is, use a model like:
public class Company {
#SerializedName("CompanyName")
String name;
public String getName() {
return name;
}
}
This would let you access the company's name property and call getCompanyName(company.getName()). Retrofit would format the company's name into the path and you'd call the right url.

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

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.

Retroft GET Request Call

There are a lot of examples but I didn't find what I really wanted (didn't get it) so if anyone would like to help it would be appreciated.
using post I know we do the following:
#POST("/SearchFor")
#FormUrlEncoded
void SearchFor(#Field("QueryID")String QueryID,#Field("ElementID")String ElementID,#Field("Language")String Language, Callback<Response> cb);
Then implement on success and on failure.
now on GET:
The URL I want as an output is :
URL+=BASE_URL+"?QueryID="+ID+"&ElementID=where%20"+INPUTFLDDBNAME+"%20like'%25"+Value+"%25'&Language="+interfacelang+"";
ID,INPUTFLDDBNAME,interfacelang will be replaced by strings
So I did the following:
#GET("/QueryID={QueryID}&ElementID=where%20{ElementID}%20like'%25{Value}%25'&Language={Language}")
void SearchFor(#Path("QueryID")String QueryID,#Path("ElementID")String ElementID,#Path("Value")String Value,#Path("Language")String Language, Callback<Response> cb);
but obviously it didn't work and got the following error:
No Retrofit annotation found. (parameter #1)
this is how im calling it
Retrofit Retrofit=new Retrofit();
RestAPI PushNotificationWebService=Retrofit.SetRestAdapter("BASE_URL");
PushNotificationWebService.SearchFor(ID,fields.get(0).getText().toString(),INPUTFLDDBNAME,interfacelang,new Callback<Response>() {
#Override
public void success(Response response, Response response2) {
...
Please someone guide me it would be much appreciated.
You should use #Query() to add the query params to your base URL
#GET("/SearchFor")
void SearchFor(#Query("QueryID") String QueryID, #Query("ElementID") String ElementID,#Query("Language") String Language, Callback<Response> cb)
Then use something along the lines of:
String element = URLEncoder.encode(String.format("where %s like'%%%s%%'", element_id, value));
PushNotificationWebService.SearchFor(ID, element, ...)

Spring 3/Square Retrofit - MultipartParams but no MultipartFiles

I have the following controller set up:
#PreAuthorize("hasAuthority('ROLE_USER')")
#RequestMapping(value = "/me/avatar", method = RequestMethod.POST)
public #ResponseBody boolean setAvatar(Principal principal, MultipartHttpServletRequest request) {
String username = ((User) ((OAuth2Authentication) principal).getPrincipal()).getUsername();
MultipartFile file = request.getFile("avatar");
return Boolean.TRUE;
}
And when I use Square Retrofit to POST to this controller:
#Multipart
#POST("/user/me/avatar?access_token={access_token}")
void uploadAvatar(#Name("access_token") String accessToken, #Name("avatar") TypedFile image, retrofit.http.Callback<Boolean> callback);
I get a MultipartHttpServletRequest which has the "avatar" parameter, with the proper file name and everything, but no multipart files.
What am I doing wrong that would cause me to get MultipartParams but no MultipartFiles? I've tried various other TypedOutput formats, but I get the same result. If I hit the same controller from Postman (a Chrome plugin) everything works as expected, leading me to think it's a bug in Retrofit?
This was due to a bug in Retrofit, which has been fixed as of today. The above code now works to upload a file from Retrofit to a Spring based api server.

Categories

Resources