Wrap a REST API with Retrofit
Building a type-safe wrapper around a REST API is a pain. You need to deal with separate HTTP calls, dynamic construction of query strings, casting to your data types, handling exceptions etc. Luckily the smart folks at Square released a tool called Retrofit to help you cope with those problems. Retrofit asks you to provide an interface to the REST API in the form of a Java interface along with some annotations. It takes care of all the HTTP calls, query string construction, and exception handling, and leaves you with some basic POJO creation. We will take a look at how to wrap the St. Louis Fed’s REST API to retrieve macro economic data. The first thing you do is create POJOS for each of the objects you would like to pass to your application.
Source code: Freddie
Create a simple POJO you want to populate with data
public class Series
{
private String id;
private String title;
private List<Observation> data;
private String seasonalAdjustment;
private String seasonalAdjustmentShort;
private String frequency;
private String frequencyShort;
private String units;
private String unitsShort;
private Date realtimeStart;
private Date realtimeEnd;
private Date observationStart;
private Date observationEnd;
private Date lastUpdated;
private Integer popularity;
private String notes;
}
Create an interface that describes the API endpoint
The interface will create the query string for you. Every method description you add here becomes one possible API endpoint call. GET, POST, PUT, DELETE, and HEAD are available. The @Query annotations allow you to pass dynamic data to these endpoints. (more info)
import retrofit.http.GET;
import retrofit.http.Query;
public interface IFredApiService
{
@GET("/series")
Series getSeries(
@Query("series_id") String seriesName,
@Query("api_key") String apiKey,
@Query("file_type") String dataReturnType);
}
Create a custom GSON deserializer
Retrofit has chosen Google’s GSON library as it’s primary serialization/deserialization library. Here is the only interesting snippet from the Series Deserializer:
@Override
public Series deserialize(
JsonElement json,
Type arg1,
JsonDeserializationContext arg2)
throws JsonParseException
{
Series series = new Series();
JsonObject obj = (JsonObject) json;
if (obj.has("seriess"))
{
JsonArray seriesCollection =
(JsonArray) obj.get("seriess");
if (seriesCollection.size() == 1)
{
JsonObject seriesObj =
(JsonObject) seriesCollection.get(0);
series.setId(
seriesObj.get("id").getAsString());
series.setTitle(
seriesObj.get("title").getAsString());
series.setFrequency(
seriesObj.get("frequency").getAsString());
.... more getting and setting ....
}
}
}
Now tie everything together in a client
Here is what we need:
Our Series Deserializer:
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.setDateFormat("yyyy-MM-dd")
.registerTypeAdapter(Series.class, new SeriesDeserializer())
.create();
Hook your GSON deserializer up to Retrofit’s magic RestAdapter:
RestAdapter adapter = new RestAdapter.Builder()
.setEndpoint(endPoint)
.setLogLevel(LogLevel.NONE)
.setConverter(new GsonConverter(gson))
.build();
The RestAdapter instantiates an object with all the methods defined in your API interface when you pass it the class name of your interface like so:
IFredApiService service = restAdapter.create(IFredApiService.class);
And that’s really it
Now you can do the following:
public Series getSeriesById(String seriesId) throws Exception
{
Series series = null;
try {
series = service.getSeries(seriesId, apiKey, "json");
} catch (Exception e) {
throw e;
}
return series;
}
Retrofit even allows for the use of Observable<?> return types which are provided by Netflix’s RxJava library. This approach makes the user of your service agnostic as to whether he calls you synchronously or asynchronously. You as the provider of the service get to decide and the consumer does not have to change the way he calls your service when you decide to execute your processes asynchronously. Read more about RxJava
Happy wrapping!
Let me know what you think => @bweidlich