Retrofit 2 removes characters after hostname from base url - android

I'm using Retrofit to access a RESTful api. The base url is:
http://api.example.com/service
This is the code for the interface:
public interface ExampleService {
#Headers("Accept: Application/JSON")
#POST("/album/featured-albums")
Call<List<Album>> listFeaturedAlbums();
}
and this is how I send request and receive the responce:
new AsyncTask<Void, Void, Response<List<Album>>>() {
#Override
protected Response<List<Album>> doInBackground(Void... params) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://api.example.com/service")
.addConverterFactory(GsonConverterFactory.create())
.build();
ExampleService service = retrofit.create(ExampleService.class);
try {
return service.listFeaturedAlbums().execute();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Response<List<Album>> listCall) {
Log.v("Example", listCall.raw().toString());
}
}.execute();
the log that I get is the weird thing:
V/Example﹕ Response{protocol=http/1.1, code=404, message=Not Found, url=http://api.example.com/album/featured-albums}
What's going on here?

Retrofit 2 uses the same rules that an <a href=""> would.
The leading / on your relative URL tells Retrofit that it is an absolute path on the host. Here's an example from a presentation I gave showing this:
Note the incorrect URL which was resolved at the bottom.
By removing the leading /, the URL then becomes relative and will combine with the path segments which are part of the base URL. Corrected in the presentation the final URL is now correct:
In your example you do not have a trailing / on the base URL. You probably want to add one so that relative paths are resolved on top of it rather than as a sibling of it.

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 Image from .NET Web API 2 Service to Android Retrofit

I have an image on a server stored in a file (not db). I want to display this image in a layout view in Android Studio. I am using Web API2 and retrofit to exchange data.
Using retrofit, I know I need to send the file encapsulated in a class. I am not aware of what type to create? (Byte array?) and how retrofit on the android side would convert this type. I have tried to use byte[] on boths side however retrofit was not able to read the byte[] from Json.
Would anyone be able to guide me on how I would go about transferring this jpeg image? Thanks!
I've recently implemented it.
This is the method in my api.
[HttpPost]
public IHttpActionResult Upload()
{
var httpRequest = HttpContext.Current.Request;
if (httpRequest.Files.Count > 0)
{
foreach (string file in httpRequest.Files)
{
var postedFile = httpRequest.Files[file];
// Do something with file.
}
else
{
// No files.
}
}
The class you are looking for is retrofit's TypedFile class.
This is my implementation.
RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint("baseurl").build();
ApiTaqueria api = restAdapter.create(ApiTaqueria.class);
TypedFile foto = new TypedFile("multipart/form-data", new File("path"));
api.subirLogoTaqueria(foto, new Callback<Taqueria>() {
#Override
public void success(Taqueria result, Response response) {
// Do something.
}
#Override
public void failure(RetrofitError retrofitError) {
// Do something.
}
});
In the retrofit interface.
#Multipart
#POST("/api/Photo/Upload")
public void subirLogoTaqueria(#Part("foto") TypedFile foto, Callback<Taqueria> callback);
Happy coding.

Retrofit 1.6: Call RestService with different (TCP)-Ports

I have to communicate with the following four RESTServices.
Germany (Default): http://url.com/suggest?query=
Austria http://url.com:82/suggest?query=
Swiss: http://url.com:83/suggest?query=
Spain: http://url.com:84/suggest?query=
Basically I have to call the same RESTService on different TCP-Ports for each Country. When I create a Retrofit-RestAdapter, I have to provide a Endpoint (base-url):
RestAdapter.Builder builder = new RestAdapter.Builder();
ObjectMapper mapper = new ObjectMapper();
builder.setEndpoint("http://url.com");
If I want to access those four RESTServices mentioned above, do I have to create a RestAdapter for each of them? Or is it possible to use only one RestAdapter-instance?
I tried to solve the problem by adding the TCP-Port as part of the RestInterface-annotation, but this does not work:
public interface AutoSuggestRemote {
#GET (":{port}/suggest")
public Response getSuggestions(#Path ("port") Integer httpPort, #Query ("query") String query);
}
I get the following exception in Logcat:
java.lang.IllegalArgumentException: AutoSuggestRemote.getSuggestions: URL path ":{port}/suggest" must start with '/'.
at retrofit.RestMethodInfo.methodError(RestMethodInfo.java:123)
at retrofit.RestMethodInfo.parsePath(RestMethodInfo.java:212)
at retrofit.RestMethodInfo.parseMethodAnnotations(RestMethodInfo.java:165)
at retrofit.RestMethodInfo.init(RestMethodInfo.java:133)
at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:294)
at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
at $Proxy3.getSuggestions(Native Method)
Therefore my question, if I have to create a RestAdapter-instance for each RESTService, or is there a way to communicat with all four services by using the same RestAdapter-instance.
Retrofit consults the EndPoint class each times it does a request. As previously answered by #JakeWharton in the question Dynamic Paths in Retrofit you could extend the EndPoint class with your own implementation and dynamically set the appropriate port as desired.
Here's the code provided by #JakeWharton modified for your specific purpose.
public final class FooEndpoint implements Endpoint {
private static final String BASE = "http://192.168.1.64:";
private String url;
public void setPort(String port) {
url = BASE + port;
}
#Override public String getName() {
return "default";
}
#Override public String getUrl() {
if (url == null) throw new IllegalStateException("port not set.");
return url;
}
}
You can then use the reference to this FooEndPoint instance to change the port dynamically or once when you initialise.
If you choose to set the port once when initialized then you would simply do this.
FooEndPoint endPoint = new FooEndPoint();
endPoint.setPort(loadPortFromSomeWhere());
RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setEndpoint(endPoint);
This will allow you to use a single RestAdapter with multiple ports.

Retrofit and Slash characters in PATH

I am facing an issue with Retrofit and would like to find a suitable answer as the only way I can think of it is pretty ugly and not practical.
Retrofit PATH annotation requires a "/" in the beginning (as you can read in this code extracted from the library source:
/** Loads {#link #requestUrl}, {#link #requestUrlParamNames}, and {#link #requestQuery}. */
private void parsePath(String path) {
if (path == null || path.length() == 0 || path.charAt(0) != '/') {
throw methodError("URL path \"%s\" must start with '/'.", path);
}
The problem that I am facing is that the PATH part comes from the backend in a response object, meaning that all PATH's strings already come formatted from the backend previously in other response as follows:
Object : {
href: "/resources/login..."
}
As you can see, when including something like this, the URL gets malformed:
#GET("{/loginHref}")
void login(#EncodedPath("loginHref") String loginHref,
Callback<User> callback);
to something like "http://mybaseurl.com//resources/login" *double // in front of resources
This can definitely cause issues in some endpoints and I cannot think a really simple way to solve this issue apart from doing something like:
a) Modify my own version of retrofit to remove that / character check (this is a last resort)
b) Truncate the href before using the method from the interface (which I would like to avoid at all cost as well as would add unnecessary transformation all over the place.
c) Intercept the request and correctly form the URL in case this scenario happens (really ugly solution as well).
Any idea, suggestions?
Thanks!
I think this link will help you Path Replacement
Your new implementation will look like this.
#GET("/")
void login(Callback<User> callback);
You can supply a custom Endpoint implementation on which you can change the relative path.
public final class CustomEndpoint implements Endpoint {
private static final String BASE = "http://192.168.1.64:5050/api/";
private String url;
private String href;
public CustomEndpoint(String href){
this.href = href;
url = BASE + this.href;
}
#Override public String getName() {
return "default";
}
#Override public String getUrl() {
if (url == null) throw new IllegalStateException("relative path not set.");
return url;
}
}
Usage is as follows
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(new CustomEndPoint(object.href));
then restadapter.create........
Hope this will help you.

Categories

Resources