I have several URL that accepts the same GET parameters (mainly for pagination purposes) as follow :
public interface AsynchronousApi {
#GET("/api/users")
public void listUsers(#Query("limit") Integer limit,
#Query("offset") Integer offset,
Callback<userList> callback);
#GET("/api/posts")
public void listPosts(#Query("limit") Integer limit,
#Query("offset") Integer offset,
Callback<postList> callback);
...
}
Since I have lots of URL, this is getting a bit repetitive.
So I would like to have way to refactor this so I don't have to repeat #Query("limit") and #Query("offset") everytime. Maybe another annotation would help ?
No. Retrofit values the semantic weight of separate methods much more than de-duplication of interface method declarations.
While the similar behavior of the API endpoints is good API design, it would be poor Java design. If this service was local (i.e., inside your app) you wouldn't consolidate the two methods because they fetch two very different things.
See this other question
Apparently retrofit added an #Url annotation for that purpose.
public interface APIService {
#GET
Call<Users> getUsers(#Url String url);
}
This feature has been added in version 2.0.0.
Related
I have an app with a lot of Retrofit endpoints. I need to run this app in the emulator without internet because I do not have access anymore to the server, I am happy with fake data, so for instance if is an Int I would be happy with a random number, if is a string with whatever string.
Also I want to be able to test this app, how can I create dummy json files on the basis of the data classes in moshi, interface endpoints?
In theory on the base of all the moshi data classes, I could write some fake data, but it will take me weeks
I know there are a number of nice tools as RESTMock, but they always follow an implementation as
RESTMockServer.whenGET(RequestMatchers.pathEndsWith("/data/example.json")).thenReturnFile(
"users/example.json");
but I want to know how to automate the process, without writing a json file myself
It should be your choice of the level on which to mock. You can mock jsons if you use rest mock server, but you can go at the higher level and mock entity that actually uses your retrofit interface, or actually mock rest interface itself:
public interface RESTApiService {
#POST("user/doSomething")
Single<MyJsonResponse> userDoSomething(
#Body JsonUserDoSomething request
);
}
public class RestApiServiceImpl {
private final RESTApiService restApiService;
#Inject
public RestApiServiceImpl(RESTApiService restApiService) {
this.restApiService = restApiService;
}
public Single<MyUserDoSomethingResult> userDoSomething(User user) {
return restApiService.userDoSomething(new JsonUserDoSomething(user))
.map(jsonResponse -> jsonResponse.toMyUserDoSomethingResult());
}
}
Clearly you can pass mock version of RESTApiService into RestApiServiceImpl and let it return hand-mocked responses. Or moving same direction you could mock RestApiServiceImpl itself and thus mock not at the json models level, but entities level.
I'm still beginner on retrofit API for android, but I still didn't get it !!
I know about the Annotation #Path and #Query but I still don't know what is the use of #Field
and I also know about #POST and #GET but I don't know what is #PUT
and one last question.. lets say that in my API I created the following service.
#GET("/bookmarks")
public abstract void bookmarks(#Query("countryCode") String paramString, #Query("limit") int paramInt1, Callback<BookmarksResult> paramCallback);
how this call is actually presented as a link?? I mean would it be like this
http://www.example.com/api/bookmarks?countryCode=X&limit=X
please some help my whole day on this and I still don't have good answers
thanks
if your baseUrl is http://www.example.com/api the answer is yes. The url will be resolved in
http://www.example.com/api/bookmarks?countryCode=X&limit=X
and the same applies for the other request methods.
I would use #QueryMap instead of passing more than one #Query, but that's more a matter of taste.
I just notice that your method is marked as abstract. I am pretty sure that one of the constraints of retrofit is that you have to use an interface to declare your endpoints
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.
I'm using an external service like :
http://domain.com/free/v1/servicename.ext?format=json&num_of_days=4
I try to use Retrofit like that :
#GET("/free/v1/servicename.ext?format=json&num_of_days={numOfDays}")
void serviceName(#Path("numOfDays") int numOfDays, Callback<Result> callback);
but an exception is thrown :
URL query string must not have replace block.
Is it compatible with this kind of url ?
It absolutely is compatible with it!
You can't use #Path inside of the query parameters. That annotation is only for replacements inside the path.
The #Query parameter allows for creating dynamic query parameters.
#GET("/free/v1/servicename.ext?format=json")
void serviceName(#Query("num_of_days") int numOfDays, Callback<Result> callback);
I would like to always add a parameter to my Retrofit calls. For values that I can hard code I can simply use
#POST("/myApi?myParam=myValue")
but what if I want to append android.os.Build.MODEL?
#POST("/myApi?machineName="+ Build.MODEL)
doesn't work. It would be useful to be able to abstract this part of the network call away from the implementing code.
EDIT
I can add Build.MODEL to all of my api calls by using a RequestInterceptor. However it still eludes me how to add it selectively to only some of my api calls while still using the same RestAdapter.
EDIT 2
Fixed the title which was all sorts of wrong.
EDIT 3
Current implementation:
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("myapi")
.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestInterceptor.RequestFacade request) {
request.addQueryParam("machineName", Build.MODEL);
}
})
.build();
API_SERVICE = restAdapter.create(ApiService.class);
Build.MODEL is not available for use in an annotation because it cannot be resolved at compile time. It's only available at runtime (because it loads from a property).
There are two ways to accomplish this. The first is using a RequestInterceptor which you mention in the question.
The second is using a #Query parameter on the method.
#POST("/myApi")
Response doSomething(#Query("machineName") String machineName);
This requires you to pass Build.MODEL when invoking the API. If you want, you can wrap the Retrofit interface in an API that's more friendly to the application layer that does this for you.