3

I am in the process of writing a neat Android application to interact with the API, that follows the Google Android user experience.

The application will be using Retrofit (to make REST-api mechanisms easier).

Now, I have created Java classes that corresponds to each API call.

For example, Site.java class that handles the /sites API in this fashion:

This is condensed for brevity:

public class Site{
    @SerializedName("api_site_parameter") String mApiSiteParameter = "";
    // snip
}

The interface is wired up in this fashion, called ISites.java

public interface ISites{
    @GET("/sites")
    public void getAllSites(@Query("filter") String filterName, 
                            Callback<List<Site>> cbAllSites);
}

The big question is, since the common wrapper is used, how can I make Retrofit return back a List<Site> collection regardless, this could equally apply to other objects such as Question, Answer and so on.

After testing with Postman extension under Chrome, I observed the following, which is a stumbling block, the common wrapper is returned in the response, regardless of which REST-api to use, however, the items field within the JSON output, contains the array of Site in this case.

I deduced, that there's a field in the common wrapper that is not returned, called type The idea here is to cheat a little bit later on... and, since it's not part of the filter, which meant having to create a new filter via /filter/create API and apply that as part of invocation to the Retrofit's RestAdapter call, as in:

RestAdapter ra = new RestAdapter.Builder()
   .setServer("http://api.stackexchange.com/2.1")
   .setLogLevel(LogLevel.FULL)
   .build();
ISites sites = ra.create(ISites.class);
sites.getAllSites("my_commonwrapper_filter", Callback<Site>(){
   @Override
   public void failure(RetrofitError argRetrofitError){
   }
   @Override
   public void success(List<Site> sites, Response response){
   }
});

After studying the Gson's custom TypeAdapterFactory which was answered here on SO, am I missing something here, this is what I have so far.

Created a class called SEWrapper.java which went like this:

public class SEWrapper{
    @SerializedName("items") List<Class ?> mListItems;
    @SerializedName("type") String mJsonObjType;
}

Changed around the Site.java source to use SEWrapper instead of Site, and modified the REST call.

This code which is adapted from the question on StackOverflow,

private class SEWrapperTypeAdapterFactory extends CustomizedTypeAdapterFactory<SEWrapper> {
    private SEWrapperTypeAdapterFactory() { super(SEWrapper.class); }
    @Override
    protected void beforeWrite(SEWrapper source, JsonElement toSerialize) {
       // Ignored for now as all this is Read Only operation!
    }

    @Override
    protected void afterRead(JsonElement deserialized) {
       String typeOfJsonObj = deserialized.getAsJsonObject().get("type").getAsString();
       if (typeOfJsonObj.equalsIgnoreCase("site")){
          JsonArray jSiteArray = deserialized.getAsJsonObject().get("items").getAsJsonArray();
          // Convert that to List<Site> type which I cannot figure out
       }
   }

Am out of ideas on how to get around this stumbling block.

I could, in theory, rewrite it to return a Retrofit's Response object and manually parse each JSON object along with creating a List of that type Site, it does feel cumbersome and long-winded for each of the API object returned.

Am I doing this the wrong way or have I set my expectations a little bit too high with the Retrofit library?

Community
  • 1
  • 1
t0mm13b
  • 34,087
  • 8
  • 78
  • 110

1 Answers1

3

The answer to this question was very simple when a lightbulb moment went off.

I realized that the list of items can refer to any object depending on the API call to the stackexchange.com site, so it meant a type of class that shall be used when the JSON's items get deserialized.

The solution was rather simple.

CommonSEWrapper.java:

public class CommonSEWrapper<T>{
   // snip
   @SerializedName("items") List<T> mListItems;
   // snip
}

Then to apply for the Site object, the interface for the Site's REST API would end up like this, ISites.java:

public interface ISites{
   @GET("/sites")
    public void getAllSites(@Query("filter") String filterName, 
                            Callback<CommonSEWrapper<Site>> cbAllSites);
}

and finally the execution of the REST via Retrofit would look like this:

RestAdapter ra = new RestAdapter.Builder()
        .setServer("http://api.stackexchange.com/2.1")
        .build();
ISites sites = ra.create(ISites.class);
ra.getAllSites("", new Callback<CommonSEWrapper<Site>>(){
   @Override
   public void failure(RetrofitError argRetrofitError){
   }
   @Override
   public void success(CommonSEWrapper<Site> sites, Response response){
       // sites is filled in, just like magic!
   }
});

Clean, elegant and simple, and no messing around with the innards of Retrofit's magic.

Hope this will help others who found themselves in a similar predicament that I experienced.

Enjoy.

t0mm13b
  • 34,087
  • 8
  • 78
  • 110
  • Question : every time for getting retrofit response we have to create CommonSEWrapper.java classes according to our response ? is this good way, or we can use http://stackoverflow.com/a/26555618/2382964 this answer to parse JSON one-by-one . – Tushar Pandey Oct 26 '14 at 01:35