2

While using retrofit with Gson I came across a situation where the provided type and the final types were different but require the same type parameter.

We have this method in a service:

@GET("awesome")
AwesomeCall<AwesomeObject> get();

And I want AwesomeCall to return an AwesomeResult which contains the relevant AwesomeObject or an error (bearing in mind that this 'awesome' API ignores HTTP and always returns 200, only to give an error object instead of our expected AwesomeObject)

What I want is for retrofit to parse AwesomeResult and make AwesomeCall return such object if there is no problem with the Request.

The problem I had is that retrofit only provides me with the return type of the service, but I needed to get a different object.

PS: This was done so that the service doesn't have any references to retrofit objects that would leak into higher levels in the code.

1 Answer 1

3

So, after some digging into the abysm of reflexion I discovered that you can implement ParameterizedType (since it's an interface) and use that to generate a different call from retrofit.

Inside my CallAdapter.Factory implementation I created this type:

private static class AwesomeResponseType implements ParameterizedType {
    private final Type innerType;

    public BrainResponseType(Type innerType) {
        this.innerType = innerType;
    }

    @Override
    public Type[] getActualTypeArguments() {
        return new Type[] {innerType};
    }

    @Override
    public Type getRawType() {
        return AwesomeResponse.class;
    }

    @Override
    public Type getOwnerType() {
        return null;
    }
}

and when creating the adapter I inject the inner type like this:

final Type innerType = getParameterUpperBound(0, (ParameterizedType) returnType);
ParameterizedType parameterizedType = new AwesomeResponseType(innerType);

and used the parameterizedType to create a custom CallAdapter like this:

private static class AwesomeCallAdapter<T> implements CallAdapter<AwesomeCall<?>> {

    private final Type responseType;

    private AwesomeCallAdapter(Type responseType) {
        this.responseType = responseType;
    }

    @Override
    public Type responseType() {
        return responseType;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <R> AwesomeCall<T> adapt(Call<R> call) {
        return new AwesomeCall<>((Call<AwesomeResponse<T>>)call);
    }

}

The AwesomeCall can now happily process a call that expects an AwesomeResponse:

public class AwesomeCall<T> {
    private final Call<AwesomeResponse<T>> call;

    public AwesomeCall(Call<AwesomeResponse<T>> call) {
        this.call = call;
    }

    public AwesomeResponse<T> execute() throws IOException {
        Response<BrainResponse<T>> response = call.execute();
        return response.isSuccessful() ? response.body() : errorResponseFrom(response);
    }

    private AwesomeResponse<T> errorResponseFrom(Response<AwesomeResponse<T>> response) {
        return new AwesomeResponse<>(null, new AwesomeError(response.message()));
    }

}

If you are using Guava, I believe you don't have to implement ParameterizedType. TypeToken has an API to do this:

TypeToken.of(AwesomeResponse.class).where(new TypeParameter<T>() {}, innerType).getType();

But sadly, the TypeToken in Gson doesn't support that functionality :(

Not the answer you're looking for? Browse other questions tagged or ask your own question.