Intro

A couple of months ago I decided to rewrite Liquid Bear android player from scratch. Main its feature is a popular services integration such as VK.com music and Last.fm. In previous version I had all API calls and parsing done by myself. But now I'm trying to not reinvent the wheel. Retrofit is the greatest thing to deal with API services.

Expectations

  • I write Api Service specification on app side with Retrofit. Something like this:
public interface VkApiService {  
    @GET("/audio.get")
    public void getAudio(@Query("owner_id") long ownerId,
                         @Query("count") int count,
                         @Query("start_from") int offset,
                         Callback<VkTracksResponseRoot> callback);
}
  • Call
vkService.getAudio(uid, count, offset, new Callback<VkTracksResponseRoot>() {  
            @Override
            public void success(VkTracksResponseRoot vkTracksResponseRoot, Response response){  
                // And here I'm happily get my entity class successfully parsed via GSON
            }
            @Override
            public void failure(RetrofitError error) {
                // Even in expectations there should be error handling, e.g. auth problem or anything else.
            }
        }
  • Profit

Reality

Square made a great job doing this library but they couldn't consider all the ways some API work. Both VK.com and Last.fm doesn't even care to response with HTTP errors in case if there is error. Just smile and wave, boys return success HTTP 200 OK status code and change JSON a bit to have error field.

There is an example of how JSON look like in error. With 200 OK code.

{
   "error":{
      "error_code":100,
      "error_msg":"One of the parameters specified was missing or invalid: offset is deprecated from version 5.13",
      "request_params":[
         {
            "key":"oauth",
            "value":"1"
         },
         {
            "key":"method",
            "value":"audio.get"
         },
         {
            "key":"offset",
            "value":"0"
         },
         {
            "key":"count",
            "value":"100"
         },
         {
            "key":"access_token",
            "value":"..."
         },
         {
            "key":"v",
            "value":"5.28"
         }
      ]
   }
}

Of course app crashes because of this. GSON want to parse VkTracksResponseRoot but got no one field from this entity. It lead to NPE everytime we are trying to work with returned object.

The Solution

Solution is to have custom retrofit's Callback subclass to pass to retrofit service.

public abstract class VkCallback<T extends VkResponse> implements Callback<T> {  
    @Override
    public final void success(T data, Response response) {
        if (data.getError() == null) {
            success(data);
        } else {
            failure(data.getError());
        }
    }    
    @Override
    public void failure(RetrofitError error) {
        failure(new VkError());
    }
    public abstract void success(T data);
    public abstract void failure(VkError error);
}

...where VkResponse is super class for all my VK root entities

public class VkResponse {  
    @SerializedName("error")
    VkError error;
        public VkResponse() {
        }
        public VkError getError() {
          return error;
        }
    }

So now it works as it should

vkService.getAudio(uid, count, offset, new VkCallback<VkTracksResponseRoot>() {  
            @Override
            public void success(VkTracksResponseRoot data) {
                // Here we are with entity class we got from server with **real** success result
            }
            @Override
            public void failure(VkError error) {
                // And there is error as supposed by VK devs. Fine, even with 200 OK.
            }
        });

Last.fm API has almost the same issue, so it's easy to adapt this advice to Last.fm. Make LastfmResponse as superclass of all root entities and LastfmCallback to handle request errors.

Simple hack and odd API developers logics implemented via Retrofit!