I'm running into a problem with duplicate slashes and I'm wondering if there is a solution built into Retrofit.
My server provides the base URL we should be using which will look something like this: .setEndpoint(http://some.url.com/)
The server also passes down a path URI that should be appended to that endpoint (with various requests). So my server might send back /channels/videos.
This gets handed to Retrofit through the following method
#GET("/{uri}")
void GET(#Header("Authorization") String authHeader, #Path(value = "uri", encode = false) String uri,
#Header("Cache-Control") String cacheHeaderValue, Callback<Object> callback);
This is then problematic because the URL that is getting hit with the GET method is http://some.url.com//channels/videos which will not work correctly in my situation.
I tried manually stripping out the trailing slash from my base endpoint - but I still saw a duplicate slash which I'm assuming resulted from the "/{uri}" and the /channels/videos.
I think my problem would be fixed by removing the leading slash in "/{uri}" but that isn't allowed in Retrofit. And removing the leading slash in the path URI I get back from my server isn't entirely feasible.
throw methodError("URL path \"%s\" must start with '/'.", path);
retrofit.RetrofitError: GET: URL path "{uri}" must start with '/'.
Is there another solution to this problem?
Relevant Links:
Possible duplicate but they describe it a little differently
Jake Wharton saying it should be de-duped in what I think is the situation I'm describing
Maybe what Jake Wharton was referencing
Currently unanswered issue asking Jake this same question
This issue is fixed in Retrofit 2.0.0-beta3. It requires changing the method signature/annotations in your service class. The signature for the original GET method is now:
#GET
Call<Object> GET(#Header("Authorization") String authHeader, #Url String uri,
#QueryMap Map<String, String> options, #Header("Cache-Control") String cacheHeaderValue);
This is using compile 'com.squareup.retrofit2:retrofit:2.0.0-beta3'
If you're still on the previous version of Retrofit, I solved the problem temporarily with the following helper (calling it on the uri passed to GET):
public static String validateUri(String uri) {
if (uri.charAt(0) == '/') {
uri = uri.substring(1);
}
return uri;
}
Related
I use Retrofit for most of my calls but in one of the cases, I have the full path provided in arguments. My URL is like this http://www.example.com/android.json. This URL is provided in full so I have to path it at runtime. I implement endpoint as suggested here
https://medium.com/#kevintcoughlin/dynamic-endpoints-with-retrofit-a1f4229f4a8d
but in the #GET I need to be able to put #GET(""). This does not work as I get an error saying I should provide at least one "/".
If I add the slash the URL becomes http://www.example.com/android.json/ and it does not work, the server returns forbidden. I also tried creating a custom GET interface similar to here https://github.com/square/retrofit/issues/458 but with GET and without providing a value method in the interface. Then I get another error saying value is missing.
Basically I need to be able to provide an empty or null value but retrofit does not allow that. How could I solve this problem? For now I am doing the JSON request manually but is there a way I could use retrofit for this case? I need to pass the full URL there is no way I can do endpoint http://www.example.com and #GET("/android.json").
Thanks
You can use #GET(".") to indicate that your url is the same as the base url.
#GET(".")
Observable<Result> getData(#Query("param") String parameter);
I've tried this approach, however didn't work for me.
Workaround for this issue is:
//Retrofit interface
public interface TestResourceClient {
#GET
Observable<Something> getSomething(#Url String anEmptyString);
}
//client call
Retrofit.Builder().baseUrl("absolute URL").build()
.create(TestResourceClient.class).getSomething("");
The downside of this solution is that you have to supply empty string in getSomething("") method call.
I face the same problem with Retrofit 2. Using #GET, #GET("") and #GET(".") not solved my problem.
According to the official document you can the same baseUrl and #GET argument.
Endpoint values may be a full URL.
Values that have a host replace the host of baseUrl and values also with a scheme replace the scheme of baseUrl.
Base URL: http://example.com/
Endpoint: https://github.com/square/retrofit/
Result: https://github.com/square/retrofit/
So in my case:
interface MyAPI {
#GET("http://www.omdbapi.com/")
suspend fun getMovies(
#Query("apikey") apikey: String,
#Query("s") s: String
): Response<MoviesResponse>
companion object {
operator fun invoke(): MyAPI {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://www.omdbapi.com/")
.build()
.create(MyAPI::class.java)
}
}
}
I've refactored some parts of my code which is written in Kotlin and tend to put url's in strings.xml, but when I want to point to the string in strings.xml file in annotation part of the Retrofit, I get the following error.
An annotation argument must be a compile-time constant
Here is my code:
interface SampleApiService {
#GET(Resources.getSystem().getString(R.string.sample_url))
fun getSamples(){
}
}
Could anyone please tell me what is wrong?
Found the answer in the following post.
As documentation states it compiles your application resources at build time. and Annotation processing takes place at compile time. So you see you cannot pass resources string to a annotation
The Android SDK tools compile your application's resources into the
application binary at build time.
instead create static string variable and pass it in annotation
Problem solved,
Tnx to Retrofit 2 - Dynamic URL
I had to use another annotation mark of retrofit.
New: #Url parameter annotation allows passing a complete URL for an endpoint.
Here is the result:
interface SampleApiService {
#GET
fun getSamples(
#Url url: String =
Resources.getSystem().getString(R.string.sample_url)
)
: Deferred<ArrayList<Sample>>
}
Based upon the example provided - one could as well just use a static URL. It would need to be annotated alike this, in order not to use any run-time values and be able to change it at run-time:
#GET("{path}")
fun getSamples(#Path("path") path: String) {}
Then one can load whatever String path from string resources, at run-time. When the base URL shall be changed, one may need to reconfigure the client. That is because this interface definition is being used by the annotation processor to generate the abstraction layer from it - at compile time already, not at run-time... when taking the complaint literal, it would have to look alike this:
#GET(Constants.SOME_PATH)
fun getSamples() {}
but there is little advance over just hard-coding that String, because it cannot be changed later.
I am trying to make an API call to a mobile backend with Retrofit 2.0. In my API call, i have to make necessary call to this URL
https://api.backendless.com/v1/data/Users?where=followings.objectId=%270B3BA7F9-260F-B378-FF9A-3C2448B8A700%27
To form this URL in Retrofit i have been using below interface
#GET("Users?where=")
Call<List<User>> getFollowers(#Query("followings.objectId") String objectId);
This interface call puts an ampersand before the query parameters and generates a URL like below
https://api.backendless.com/v1/data/Users?where=&followings.objectId=%270B3BA7F9-260F-B378-FF9A-3C2448B8A700%27
I tried to overcome this with Path annotation but i keep getting "URL query string must not have replace block. For dynamic query parameters using #Query" error.
API that i am trying to connect requires a "where=" clause for filtering by design. I have no permission to change that. What i want is somehow tell Retrofit not to put an ampersand sign before the query parameter or any workarounds for this issue.
Any help is appreciated.
For those who seek for similar answers, I came up with below solution
I declared my interface with #Url
#GET
Call<List<User>> getFollowers(#Url String objectId);
and generated related URL part as an extension method
public String toFollowerRequest(){
return RestGlobals.BASE_URL + "Users?where=followings.objectId=%27" + objectId + "%27";
}
#GET("{path}")
Call> getFollowers(#Path("path") path, #Query("followings.objectId") String objectId);
getFollowers("Users?where=", ...)
Editing question with more details :
I understand the use of service interfaces in Retrofit. I want to make a call to a URL like this :
http://a.com/b/c (and later append query parameters using a service interface).
My limitations are :
I cannot use /b/c as a part of service interface (as path parameter). I need it as a part of base url. I have detailed the reason below.
I cannot afford to have a resultant call being made to http://a.com/b/c/?key=val. What I need is http://a.com/b/c?key=val (the trailing slash after "c" is creating problems for my API). More details below.
My Server API changes pretty frequently, and I am facing trouble on the client side using Retrofit. The main problem is that we cannot have dynamic values (non final) passed to #GET or #POST annotations for Path Parameters (like it is possible for query parameters). For example, even the number of path parameters change when the API changes. We cannot afford to have different interfaces everytime the API changes.
One workaround to this is by forming the complete URLs, that is, an Endpoint with Base_Url + Path_Parameters.
But I am wondering why is Retrofit forcibly adding a trailing slash ("/") to the base url :
String API_URL = "https://api.github.com/repos/square/retrofit/contributors";
if (API_URL.endsWith("/")) {
API_URL = API_URL.substring(0, API_URL.length() - 1);
}
System.out.println(API_URL); //prints without trailing "/"
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(API_URL)
.build();
API_URL is always being reset to https://api.github.com/repos/square/retrofit/contributors/ by Retrofit internally (confirmed this by logging the request)
One workaround to this is by manually adding a "?" in the end to prevent "/" to be added: https://api.github.com/repos/square/retrofit/contributors?
Unfortunately, such request won't be accepted by our API.
Why is Retrofit forcing this behavior ?
Is there a solution for people like me who don't want a trailing slash ?
Can we have variable parameters (non final) being passed to Retrofit #GET or #POST annotations ?
You're expected to pass the base URL to the setEndpoint(...) and define /repos/... in your service interface.
A quick demo:
class Contributor {
String login;
#Override
public String toString() {
return String.format("{login='%s'}", this.login);
}
}
interface GitHubService {
#GET("/repos/{organization}/{repository}/contributors")
List<Contributor> getContributors(#Path("organization") String organization,
#Path("repository") String repository);
}
and then in your code, you do:
GitHubService service = new RestAdapter.Builder()
.setEndpoint("https://api.github.com")
.build()
.create(GitHubService.class);
List<Contributor> contributors = service.getContributors("square", "retrofit");
System.out.println(contributors);
which will print:
[{login='JakeWharton'}, {login='pforhan'}, {login='edenman'}, {login='eburke'}, {login='swankjesse'}, {login='dnkoutso'}, {login='loganj'}, {login='rcdickerson'}, {login='rjrjr'}, {login='kryali'}, {login='holmes'}, {login='adriancole'}, {login='swanson'}, {login='crazybob'}, {login='danrice-square'}, {login='Turbo87'}, {login='ransombriggs'}, {login='jjNford'}, {login='icastell'}, {login='codebutler'}, {login='koalahamlet'}, {login='austynmahoney'}, {login='mironov-nsk'}, {login='kaiwaldron'}, {login='matthewmichihara'}, {login='nbauernfeind'}, {login='hongrich'}, {login='thuss'}, {login='xian'}, {login='jacobtabak'}]
Can we have variable parameters (non final) being passed to Retrofit #GET or #POST annotations ?
No, values inside (Java) annotations must be declared final. However, you can define variable paths, as I showed in the demo.
EDIT:
Note Jake's remark in the comments:
Worth noting, the code linked in the original question deals with the case when you pass https://api.github.com/ (note the trailing slash) and it gets joined to /repos/... (note the leading slash). Retrofit forces leading slashes on the relative URL annotation parameters so it de-dupes if there's a trailing slash on the API url.
#Get("/ServiceTest?version=1&dateRange={dateRange}")
ResponseModel getDateRangeTest(String dateRange);
in RestClient interface this make the following Get
http://localhost:8080/ServiceTest/2014-01-29%25202014-01-29?version=1" resulted in 400 (Bad Request); invoking error handler
Am i doing something wrong in sending the query params.
I want address this way:
/ServiceTest?version=1&dataRange=2014-01-29%25202014-01-29
Which in this case somehow i am failed to generate with Android annotations #Get
Answer: My calls were not correct and main problem was with uri encode, correct one is
/ServiceTest?version=1&dataRange=2014-01-29%202014-01-29
you might do wrong.
Your server get /ServiceTest
but you access server with address /ServiceTest/2014-01-29%25202014-01-29
Look careful that your server receive as /ServiceTest?version=1&dateRange={dateRange}
and {dataRange} is what you intend to give as variable.
Your address should be /ServiceTest?version=1&dataRange=2014-01-29%25202014-01-29
== EDIT ==
I'm not familiar with Android Get annotation, but try this.
#Get("/ServiceTest?version=1&dateStart={dateStart}&dateEnd={dateEnd}")
ResponseModel getDateRangeTest(String dateStart, String dateEnd);
and access with /ServiceTest?version=1&dataStart=2014-01-29&dateEnd=2014-01-29
Note that I change argument for more specific. and it would be a better approach.