How I got the best of Retrofit

Thanks to Jake Wharton

Last week, for the traditional annual hackathon at my company, I created a small app that would use our in-house scheduling service’s API to remind Android Wear’s users for upcoming meetings and allow them to (1) not forget it, (2) know where it is about to take place and (3) send comments back―via voice, emoji or shortcut actions―to the meeting’s record in case they were running late or had any requests while being on the move.

I also got to watch one amazing talk from Jake Wharton: “Making Retrofit work for you”―Retrofit is a Type-safe HTTP client for Android and Java by Square, Inc. Our in-house service uses envelopes for the API response and also has a mix of Json and Xml APIs ; both problems Jake suggested Retrofit solutions to in his talk. The app I made during the hackathon dealt with other questions but I found these Retrofit’s tips so helpful I decided to only focus on them on this post.

Dealing with Envelopes

Here would be a enveloped response I would receive from the login API:

{
"success": true,
"context": {
"loginUser": {
"id": 17,
"name": "Robert Diggs"
},
"requestToken": "ac350somegdgdfhdhstuffb9250cb6fa",
"sessionToken": "JSESSIONID=baYBletcstufflVAH"
}

}

I am only interested in the data inside the context object and I would simply like to define my call as Call<LoginContext> login(); rather than Call<LoginResponse> login(); so I would not have to deal with unwrapping it everytime. This is rather easy to do with Retrofit and here is how:

  1. Create a customized converter factory extending retrofit2.Converter.Factory.
  2. Override Converter<ResponseBody, ?> responseBodyConverter().
  3. Return null when the type of the response is not LoginContext so we allow retrofit to look for the next converter.
  4. Ask retrofit for a converter that can handle the wrapper LoginResponse.
  5. Return a new converter that would use the above converter to unwrap LoginResponse and return just the LoginContext, what we initially wanted.
  6. Done.

Here is the code

Now I don’t need to care about the wrapper and can work with the login context from the start.

Dealing with mixed APIs

So we have one Json API and one XML API. Why can mixing them become a problem? You can stack up converter into retrofit like below:

new Retrofit.Builder()
.addConverterFactory(SimpleXmlConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();

Retrofit will then, in the added order, pass the type of the object defined in the API call signature like Call<LoginContext> login(); and ask each converter if it can deal with it. If yes, it will convert the data, otherwise will delegate the conversion to the next converter. The thing is that both SimpleXmlConverter and GsonConverter handle everything, which means the above code will never allow the Gson converter to convert anything since the SimpleXml will say ‘yes’ to any. The same problem would happen in the reversed order.

The easy way to fix this would be to create two different services with two different retrofit instances. But what about the simple way ? Here is how to do it:

  1. Create two arbitrary annotations, say @XML and @Json, and add them to their respective API calls signature.
  2. Create a customized converter factory extending retrofit2.Converter.Factory.
  3. Check the API call annotation, whether it contains @XML or @Json, and delete the convert process to the right one.
  4. Done.

Here is the code:

Now I can only use one service without worrying whether I am calling my Json or XML API. Jake also presents a more generic way to build it in his talk.

Like what you read? Give Benoît Quenaudon a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.