Android Retrofit2: how to pass request query parameters to response? - android

I'm writing a library for a REST API. The library will have to hide to the user the internal complexity of the API as much as possible.
I'm relying on retrofit2 configured to use RxJava to wrap the responses and GSon to parse them.
I'm using retrofit for a REST service.
#GET("search")
Observable<SearchResult> search(#Query("q") String query);
#GET("search")
Observable<SearchResult> search(#QueryMap Map<String, String> params);
#GET("search")
Observable<SearchResult> searchNext(#Query("q") String query, #Query("startKey") String startKey);
#GET("search")
Observable<SearchResult> searchNext(#QueryMap Map<String, String> params, #Query("startKey") String startKey);
The search REST service return a JSON
{
"count": 2334,
"nextBatchKey": "SADjdfsahoi023451sadfjlskdfj02134512",
"results": [ .... ]
}
the total count of result
a key to be used to retrieve the next batch of the list
the actual array of results
To obtain the next batch the service has to be called again with the same parameters and the key returned.
For example, suppose doing a search for q=foo and limit=20: the REST API will return the results matching string foo batched in segments for 20 results each. To get the next batch I would need to create another request with the same two parameters, q=foo and limit=20, adding startKey=<the next batch key>.
I want / need to hide from the user of the library these internal mechanism.
Inside my library I initialize retrofit like this:
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient) // added interceptors
.baseUrl(baseUrl) // my base url
.addConverterFactory(GsonConverterFactory.create(gson)) // custom type adapter factory
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
It generate the service for my interface and use Gson to parse the response wrapping it with RxJava Observable.
Then I obtain the service:
MyService service = retrofit.create(MyService.class)
Note that I do not write the code of this service, as retrofit works it generate the service automatically using my interface (MyService) annotations.
From the library user perspective all this is hidden, he will just receive the service and use it like this:
// obtain the service
MyService service = MyLibrarySDK.getService(context);
// perform the search operation
Observable<SearchResult> resultObservable =
service.search(params);
I want the developer using my library to be able to get the next batch like this:
// then later result == the SearchResult
Observable<SearchResult> nextBatch =
result.searchNext();
The searchNext() call takes no parameters. This means the SearchResult object I created in the previous call should internally know which parameters to use when using the method searchNext(Map parameters, String key).
If the user perform the search call with q=foo this information is not received in the response. And I need it.
Since retrofit create the service I don't know how I can intercept the method parameters passed with the call to service.search().
The only idea I had to address this is to use an OkHttp interceptor, place the query parameters map in a ThreadLocal, then use a custom Gson TypeAdapter to inject those parameters into the SearchResult object.
This solution should technically work but I think it's ugly. Furthermore the interceptor would run for any method of the REST API but I need it only for search queries.
Have you got a better / creative / elegant idea?

Don't think you should have have searchNext() on the result but rather on the service.
Try something like below:
MyService.java
public class MyService {
RestClient.GitApiInterface service = RestClient.getClient();
String query;
Observable<GitResult> resultObservable;
public Observable<GitResult> getUsersByName(String query) {
this.query = query;
resultObservable = service.getUsersByName(query, 0).cache();
return resultObservable;
}
public Observable<GitResult> searchNext() {
Observable nextResultObservable = resultObservable
.flatMap(new Func1<GitResult, Observable<?>>() {
#Override
public Observable<?> call(GitResult gitResult) {
return service.getUsersByName(query, gitResult.getNextPage());
}
});
return nextResultObservable;
}
}
Usage
final MyService service = new MyService();
// First load
service.getUsersByName("tom");
// consecutive calls
service.searchNext();
Sample app - https://gitlab.com/wizardkrishna/retrofit2-sample
References
Consuming REST API using Retrofit2
Observable Caching

Related

Android Retrofit2 optional #Path param

In my andorid app I am making a GET request using Retrofit2:
http://myapi.com/items/list
But I would also like to make another request e.g.
http://myapi.com/items/list/filter/active:true,min_price:100
So the filter parameter is optional. I am trying to do the following:
#GET("items/items/list{filter}")
Observable<ResponseItems> getItems(#Path("filter") String filter);
and calling it like:
service.getItems("")
and:
service.getItems("/filter/active:true,min_price:100")
But it does not work. So I ended up creating two separate service calls, one with filter param and other without. I think that there should be more elegant method though.
So i've seen what you are trying to achieve.
How your api declaration should looks like:
#GET("items/list/{filter}")
Observable<ResponseItems> getItems(#Path(value = "filter", encoded = true) String filter);
and a call service.getItems("") would lead to http://myapi.com/items/list/ be called
a call service.getItems("filter/active:true,min_price:100") would lead to
http://myapi.com/items/list/filter/active:true,min_price:100 be called.
An encoded property in #Path annotation is set because your optional path parameter contains / and retrofit encodes it without that property.
So as i wrote in comments better use two declarations:
#GET("items/list/")
Observable<ResponseItems> getItems();
#GET("items/list/filter/{filter}")
Observable<ResponseItems> getItems(#Path(value = "filter") String filter);
so you may call it like service.getItems("active:true,min_price:100")
In simple word make method over loading. Create two method with same name and different parameter. Check my below source code. (written in kotlin)
#GET("myportal/news/{id}")
fun getNewsList(#Path("id") id: String): Call<NewsEntity>
#GET("myportal/news/{id}/{dateTime}")
fun getNewsList(#Path("id") id: Long, #Path("dateTime") dateTime: String): Call<NewsEntity>
Its better to use HashMap to send optional params in a request, this allows you to send single/multiple optional params in your request also if you send the HashMap empty it will not affect your request.
Your interface will contain code like snippet below
#POST("yourUrl")
#FormUrlEncoded
Call<YourResponse> yourApiRequest(#Header("Authorization") String token,
#Field("mandatoryParam") int mandatoryParam,
#FieldMap Map<String, String> optionalParamMap);
And your API call will be something like the following
private HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("optionalParam1", value1);
hashMap.put("optionalParam2", value2);
ApiClient.getApiClient().getApiInterface().yourApiRequest(token,
mandatoryParam, hashMap)
.enqueue(new Callback<YourResponse>() {
#Override
public void onResponse(Call<YourResponse> call, Response<YourResponse> response) {
}
#Override
public void onFailure(Call<YourResponse> call, Throwable t) {
}
});
In a case where you don't want to send any optional params you can pass an empty hashmap to your request.

Using Retrofit and RxJava, how do I deserialize JSON when it doesn't map directly to a model object?

The API I'm working with returns objects (and their containing objects) in a "flat" format and I'm having trouble getting this to work elegantly with Retrofit and RxJava.
Consider this JSON response for an /employees/{id} endpoint:
{
"id": "123",
"id_to_name": {
"123" : "John Doe"
},
"id_to_age": {
"123" : 30
}
}
Using Retrofit and RxJava, how do I deserialize this to a Employee object with fields for name and age?
Ideally I'd like RxJava's onNext method to be called with an Employee object. Is this possible? Could this perhaps be done with some type of custom deserializer subclass (I'm using Gson at the moment)?
I realize I could create an EmployeeResponse object that maps directly to the JSON response, but having to map the EmployeeResponse to the Employee object every time I use this in an activity seems kind of unfortunate. It also gets much more complicated when the flat response also contains other objects that need to get deserialized and set as fields on the Employee.
Is there a better way?
The complete solution to this will seem like a lot, but this will let you write Retrofit interfaces with Employee instead of EmployeeResponse. Here's the game plan:
You will still need both EmployeeResponse and Employee objects, where EmployeeResponse just maps exactly to what you'd get from the API. Treat the response as a builder for Employee and write a static factory method that returns an Employee from an EmployeeResponse, ie. Employee employee = Employee.newInstance(response);
You will be creating a custom TypeAdapterFactory for Gson. When Gson sees you request a Employee object, we will have the TypeAdapter actually create an EmployeeResponse, then return the Employee via the static factory method described above.
Your TypeAdapterFactory will look something like this:
public class EmployeeAdapterFactory implements TypeAdapterFactory {
#Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return type.getRawType() == Employee.class
? (TypeAdapter<T>) employeeAdapter(gson, (TypeToken<Employee>) type)
: null;
}
private TypeAdapter<Employee> employeeAdapter(Gson gson, TypeToken<Employee> type) {
return new TypeAdapter<Employee>() {
#Override public void write(JsonWriter out, Employee value) throws IOException {
// TODO serialization logic to go from an Employee back to EmployeeResponse structure, if necessary
}
#Override public Employee read(JsonReader in) throws IOException {
return Employee.newInstance(gson.fromJson(in, EmployeeResponse.class));
}
};
}
}
Register the factory when you make Gson:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new EmployeeAdapterFactory())
.build();
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://foo.bar")
.addConverterFactory(GsonConverterFactory.create(gson))
... // your other Retrofit configs, like RxJava call adapter factory
.build();
And now you can safely define all your Retrofit interfaces with Employee instead of EmployeeResponse.

How to get response as String using retrofit without using GSON or any other library in android [duplicate]

This question already has answers here:
Unable to create call adapter for class example.Simple
(19 answers)
Closed 7 years ago.
I am trying to get response from the following Api :
https://api.github.com/users/username
But I don't know how to get response as String so that I can use the String to parse and get the JSONObject.
Retrofit version used:
retrofit:2.0.0-beta1
I have tried this until now:
public interface GitHubService {
#GET("/users/{user}")
public String listRepos(#Path("user") String user,Callback<String> callback);
}
retrieving :
GitHubService service = retrofit.create(GitHubService.class);
service.listRepos("username", new Callback<String>() {
#Override
public void onResponse(Response response) {
System.out.println(response.toString());
}
#Override
public void onFailure(Throwable t) {
}
});
exception:
Caused by: java.lang.IllegalArgumentException: Could not locate call adapter for class java.lang.String. Tried:
* retrofit.ExecutorCallAdapterFactory
at retrofit.Utils.resolveCallAdapter(Utils.java:67)
at retrofit.MethodHandler.createCallAdapter(MethodHandler.java:49)
Any help would be really appreciated.
** Update ** A scalars converter has been added to retrofit that allows for a String response with less ceremony than my original answer below.
Example interface --
public interface GitHubService {
#GET("/users/{user}")
Call<String> listRepos(#Path("user") String user);
}
Add the ScalarsConverterFactory to your retrofit builder. Note: If using ScalarsConverterFactory and another factory, add the scalars factory first.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
// add other factories here, if needed.
.build();
You will also need to include the scalars converter in your gradle file --
implementation 'com.squareup.retrofit2:converter-scalars:2.1.0'
--- Original Answer (still works, just more code) ---
I agree with #CommonsWare that it seems a bit odd that you want to intercept the request to process the JSON yourself. Most of the time the POJO has all the data you need, so no need to mess around in JSONObject land. I suspect your specific problem might be better solved using a custom gson TypeAdapter or a retrofit Converter if you need to manipulate the JSON. However, retrofit provides more the just JSON parsing via Gson. It also manages a lot of the other tedious tasks involved in REST requests. Just because you don't want to use one of the features, doesn't mean you have to throw the whole thing out. There are times you just want to get the raw stream, so here is how to do it -
First, if you are using Retrofit 2, you should start using the Call API. Instead of sending an object to convert as the type parameter, use ResponseBody from okhttp --
public interface GitHubService {
#GET("/users/{user}")
Call<ResponseBody> listRepos(#Path("user") String user);
}
then you can create and execute your call --
GitHubService service = retrofit.create(GitHubService.class);
Call<ResponseBody> result = service.listRepos(username);
result.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Response<ResponseBody> response) {
try {
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onFailure(Throwable t) {
e.printStackTrace();
}
});
Note The code above calls string() on the response object, which reads the entire response into a String. If you are passing the body off to something that can ingest streams, you can call charStream() instead. See the ResponseBody docs.

Android retrofit, serializing POST parameters

I am using Retrofit to Post form data and recieve back XML. What I have so far works fine but i want to make some changes. Here is my existing code (and it works):
Here is my interface
public interface SignupUser
{
#FormUrlEncoded
#POST("/createaccount.cfm")
SignupServerResponse signup(#Field("e") String email, #Field("p") String password);
}
Here is the code to call the api (again, this works fine, I will explain below what I want to change)
SignUpDetails mDeets; // this gets initialize and set somewhere else
RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint("http://myurl.com")
.setConverter(new SimpleXMLConverter()).build(); // the response is xml, that works fine
SignupUser service = restAdapter.create(SignupUser.class);
SignupServerResponse res = service.signup(mDeets.getE(), mDeets.getP());
How can I make it so that I can pass the SignUpDetails object straight to the signup() method instead of passing in separate Strings? When I change the constructor of signup() to accept SignUpdetails object (see below) and pass my SignUpDetails object in, I get an error saying
No Retrofit Annotation Found
Here is how I would like to define the interface
public interface SignupUser
{
#FormUrlEncoded
#POST("/createaccount.cfm")
SignupServerResponse signup(SignUpDetails deets);
}
And call it like this (instead of passing in all those parameters)
SignupServerResponse res = service.signup(mDeets);
I tried adding #Field above each of my variables in the SignUpDetails class and that doesnt work either (compilation error)
On your interface use #Body annotation for the deets parameter:
#POST("/createaccount.cfm")
SignupServerResponse signup(#Body SignUpDetails deets);
That should convert SignUpDetails into XML (since you use xml converter on your adapter).
It is then your responsibility to parse the XML request body on server.

How to get two objects - raw Response object and POJO - all at once with Retrofit?

I'm using Retrofit/OkHttp in a project and recently I've discovered RxJava. Combining it with Retrofit seems easy and straightforward but with regular async callbacks in Retrofit in success(...) we are receiving the parsed POJO and the Response. This is quite useful and in some of my callbacks I'm using both of these objects. I can't seem to find a way to do this with RxJava.
Is it possible to obtain the parsed POJO and the Response object at the same time?
The way RxJava works is that the onNext method always emits exactly one value, so you won't be able to get something like (as it would break the contract):
onNext(T value, Response respone);
The closest could be an Observable<ResponseAndPojo<T>> where ResponseAndPojo is as follows:
public class ResponseAndPojo<T> {
private final T value;
private final Response response;
public ResponseAndPojo(T value, Response response) {
this.value = value;
this.response = response;
}
// add getters here
}
Such an Observable would then emit items with:
onNext(ResponseAndPojo<T> responseAndPojo)
and you would have access to both the Response and the POJO.
Now, how to construct such an Observable:
One way would be to create some kind of Subject (maybe a BehaviorSubject, but for single requests it does not really matter) and then in the Retrofit success method put the return values into the Subject.
So, in some kind of RetrofitWrapper class of your own you would have
public Observable<ResponseAndPojo<YourPojoClass>> getResponseAndPojoObservable() {
final BehaviorSubject<ResponseAndPojo<YourPojoClass>> retrofitSubject = BehaviorSubject.<ResponesAndPojo<YourPojoClass>>create();
yourRetrofitService.getSomething(new Callback<YourPojoClass>() {
#Override
public void success(YourPojoClass pojo, Response response) {
retrofitSubject.onNext(new ResponseAndPojo(pojo, response);
retrofitSubject.onCompleted();
}
});
return retrofitSubject;
}
As you can see, from the outside the Subject looks like an Observable<ResponseAndPojo<YourPojoClass>>, which is exactly what we wanted.

Categories

Resources