I'm using retrofit to get and post some data from the server.
I need the URL from the user, so I saved them in shared preferences, I'm able to read the baseurl in the service file from shared preferences, but I can read them in interface 'JsonPlaceHolderApi' as it needs context and another error is of an attribute value must be constant.
First solution is to use url parameters like this:
public interface JsonPlaceholderApi{
#GET("{get_url}")
Call<List<Messages>> getPosts(#Path("get_url") String getUrl);
#FormUrlEncoded
#POST("{post_url}")
Call<List<MessageStatus>> PostStatus(#Path("post_url") String postUrl, ....);
}
and when you want to call your api pass that url like below:
api.getPosts(mPreferences.getString("get_url" , ""))
api.PostStatus(mPreferences.getString("post_url" , ""), ...)
There is also a tricky solution (which I personally hate it) but you can set urls to some constants and manipulate them in your Interceptor (e.g. replacing them) by reading them from shared preferences.
For example:
public interface JsonPlaceholderApi{
public static final String GET_POSTS_DUMMY_URL = "GET_POSTS_DUMMY_URL";
public static final String POST_STATUS_DUMMY_URL = "POST_STATUS_DUMMY_URL";
#GET(GET_POSTS_DUMMY_URL)
Call<List<Messages>> getPosts();
#FormUrlEncoded
#POST(POST_STATUS_URL)
Call<List<MessageStatus>> PostStatus(....);
}
And then in your retrofit Interceptor's intercept function do something like this:
public class RetrofitInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder requestBuilder = request.newBuilder();
// ...
requestBuilder.url(request.url().toString()
.replace(
JsonPlaceholderApi.GET_POSTS_DUMMY_URL,
mPreferences.getString("get_url" , "")
)
.replace(
JsonPlaceholderApi.POST_STATUS_DUMMY_URL,
mPreferences.getString("post_url" , "")
)
);
request = requestBuilder.build();
// ...
return chain.proceed(request);
}
}
And if you don't know how to add interceptor to retrofit check this document
Since the baseUrls are not going to change u can save them statically any where in a separate constants class and get them using the name of the class without using the SharedPreferences
class Consts {
public static final String GET_URL = "put_url_here";
public static final String POST_URL = "put_url_here";
}
then, in the Interface where u need any of them just place:
Consts.GET_URL
or
Consts.POST_URL
Related
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.
I need to modify some fields of object which I receive from server using Retrofit before I store it internally:
For example, server object model
{
"field1":boolean;
"field2":String
}
app object model:
{
"field1":int=boolean?1:0;
"field2":my prefix + String;
}
Should I write my own parser? Or will parsing using GSON would be enough with some interception before object is stored locally?
Why not changing them after deserilization takes place? It's not that clear what you're trying to do but here is a solution.
(1) Write a deserilizer
public class MyDeserializer implements JsonDeserializer<MyModel> {
...
}
(2) Register type adapter
Gson gson = new GsonBuilder()
.registerTypeAdapter(MyModel.class, MyDeserializer)
.create();
(3) Add it to retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(...)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
Alternatively you could just change your field's content after you get your instances.
Using GSON you can write your own JsonDeserializer.
This example shows how translate int in JSON to boolean in your object.
public class BooleanTypeAdapter implements JsonDeserializer<Boolean> {
#Override
public Boolean deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
int code = json.getAsInt();
return code != 0;
}
}
And this:
new GsonBuilder().registerTypeAdapter(boolean.class, new BooleanTypeAdapter()).create();
Hope it helps.
I'm trying to build my URL using Retrofit 2.0. The problem is it's returning this URL:
http://query.yahooapis.com/v1/public/yql?&q=select%20*%20from%20yahoo.finance.quotes%20where%20symbol%20in%20(%22YHOO%22)&format=json%26diagnostics%3Dtrue%26env%3Dstore%253A%252F%252Fdatatables.org%252Falltableswithkeys%26callback%3D
I want it to return this URL instead:
https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo.finance.quotes%20where%20symbol%20in%20(%22YHOO%22)&format=json&diagnostics=true&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=
Can anyone please advise how do I fix this?
Here is the code that returns the URL:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
mQuoteAdapter = new QuoteAdapter(items);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.question_list);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(mQuoteAdapter);
StockApiServiceInterface stockApiServiceInterface = retrofit.create(StockApiServiceInterface.class);
stockApiServiceInterface.listQuotes(
"select * from yahoo.finance.quotes where symbol in (\"YHOO\")",
"json&diagnostics=true&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=")
.enqueue(new Callback<ResultWrapper>() {
#Override
public void onResponse(Response<ResultWrapper> response) {
response.body().getQuery().getResults().getQuote().getAsk();
}
#Override
public void onFailure(Throwable t) {
Log.e("listQuotes threw: ", t.getMessage());
}
});
Here is my StockApiService:
public final class StockApiService {
public interface StockApiServiceInterface {
#GET("v1/public/yql?")
Call<ResultWrapper> listQuotes(
#Query("q") String query,
#Query("format") String env
);
}
}
Remove the question-mark form your request URL like this:
#GET("v1/public/yql")
And seperate the parameters you are sending here:
"json&diagnostics=true&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=")
Into query items.
Your method should look something like this:
#Query("q") String query,
#Query("format") String format,
#Query("diagnostics") boolean diagnostics,
#Query("env") String enviroment,
#Query("callback") boolean callback
Slight change from Ian that simplifies it a little:
public final class StockApiService {
public interface StockApiServiceInterface {
#GET("v1/public/yql?format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys")
Call<ResultWrapper> listQuotes(
#Query("q") String query,
#Query("diagnostics") boolean diagostics
);
}
}
Unchanging query strings parameters can be included in the method annotation and retrofit should merge them together. Also, I removed the callback parameter because that is a thing for websites called JSONP and isn't relevant to an Android app.
The actual problem you have is that you are giving Retrofit a precomposed partial query string and asking it to encode it for you. Retrofit doesn't know that it's a precomposed query string, so it does what it's supposed to: treats it as the value of a query string parameter and URL encodes it. #Ian is absolutely right that you need to split them up.
so I'm trying to send a simple String to my REST server from an Android app using androidannotations.
http://localhost:8080/TestServer_RESTJersey/api/lanceurs/parPays
Using Advanced REST client chrome extension, I send the parameter :
country=Europe
and it's working fine. Now my problem whith the Android app is that my request is received by the server, but the country parameter is always null. My others GET requests are all working perfectly.
Here is my RestClient class :
#Rest(converters = {MappingJacksonHttpMessageConverter.class, FormHttpMessageConverter.class})
public interface RestClient extends RestClientRootUrl, RestClientSupport{
#Get("/poke/simple")
public MessageResponse simplePoke();
#Get("/api/lanceurs/{name}")
public LaunchVehicleResponse nameRequest(String name);
//server doesn't get the parameter here...
#Post("/api/lanceurs/parPays")
public LaunchVehicleResponse countryRequest(String country);
}
Any help would be appreciated as usual, thanks!
EDIT :
server-side REST api :
#Path("api/lanceurs/parPays")
#POST
public String getLanceurByCountry(#FormParam("country") String country)
{
initData();
LaunchVehicleResponse lvr = new LaunchVehicleResponse();
ArrayList<LaunchVehicle> allv = myDatabase.getDataByCountry(country);
lvr.setData(allv);
return parseObjectToJson(lvr);
}
In JAX-RS, use #QueryParam annotation to inject URI query parameter into Java method. example,#QueryParam("country") String countryName,
Try the below, i guess, it should work
#Post("/api/lanceurs/parPays")
public LaunchVehicleResponse countryRequest(#QueryParam("country") String country);
Ok, it seems I figured out a way to get myself out of this mess.
I made a class LaunchVehicleRequest on my client, containing (among other things) a country String. When I need to send a request to my server, I instantiate this class and initialize LaunchVehicleRequest.country with the value I want (ex: "USA"). Then I send the whole object to my RestClient.
LaunchVehicleRequest lvreq = new LaunchVehicleRequest();
lvreq.setCountry("Europe");
LaunchVehicleResponse lvr = pm.countryRequest(lvreq);
...
#Rest(converters = {MappingJacksonHttpMessageConverter.class, FormHttpMessageConverter.class}, interceptors = { LoggingInterceptor.class } )
public interface RestClient extends RestClientRootUrl, RestClientSupport, RestClientHeaders{
#Post("/api/lanceurs/parPays")
public LaunchVehicleResponse countryRequest(LaunchVehicleRequest request);
}
I set up the same class on my server-side, which get the request as a string and then convert it in an object.
#Path("api/lanceurs/parPays")
#POST
public String getLanceurByCountry(String request)
{
// request={"country":"USA"}
//my json parsing function here
LaunchVehicleRequest lvreq = parseJsonToRequest(request);
...
}
I don't know is this is the best way, but hey it's working fine now and I'm using my LaunchVehicleRequest class for every different request I can need to, so it's not THAT bad I guess ^^'
Thanks everyone anyway ;)
As explained on the wiki, you can send form parameters this way:
#Rest(rootUrl = "http://company.com/ajax/services", converters = { FormHttpMessageConverter.class, MappingJackson2HttpMessageConverter.class })
public interface MyRestClient extends RestClientHeaders {
#RequiresHeader(HttpHeaders.CONTENT_TYPE)
#Post("/api/lanceurs/parPays")
public LaunchVehicleResponse countryRequest(MultiValueMap<String, Object> data);
}
MultiValueMap<String, Object> data = new LinkedMultiValueMap<>();
data.set("country, "Europe");
client.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE);
client.countryRequest(data);
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.