Retrofit: How to implement different converter factories for different API responses?

Created on 30 Jan 2017  路  4Comments  路  Source: square/retrofit

I am working with an API that responds in two ways:

1)

Success (HTTP Status Code: 200):
{
"status":"ok",
"response":{
...
}

}

Failure (HTTP Status Code: 400):
{
"status" : "error",
"runtimeError" : {
...
}
}

2)

Success (HTTP Status Code: 200):
{
...
}
Failure (HTTP Status Code: 400):
{
  "developerMessage": "SERVICE_NOT_FOUND",
  "userMessage": "小械褉胁懈褋 薪械 薪邪泄写械薪",
  "errorCode": "SERVICE_NOT_FOUND"
}

The first structure is wrapped objects in json, the second one is not.

For the first structure I created a converter factory and handle the result as described in
http://blog.davidmedenjak.com/android/2016/07/12/retrofit-converter-unwrapping.html.

Here is the source code:

Retrofit.Builder retrofitBuilder =
                new Retrofit.Builder()
                        .baseUrl(BuildConfig.URL_BASE)
                        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                        .addConverterFactory(new UnwrapConverterFactory(GsonConverterFactory.create(GsonProvider.gson)));

```java
public class UnwrapConverterFactory extends Converter.Factory {

private GsonConverterFactory factory;

public UnwrapConverterFactory(GsonConverterFactory factory) {
    this.factory = factory;
}

@Override
public Converter<ResponseBody, ?> responseBodyConverter(final Type type,
                                                        Annotation[] annotations, Retrofit retrofit) {
    Type wrappedType = new ParameterizedType() {
        @Override
        public Type[] getActualTypeArguments() {
            // -> WrappedResponse<type>
            return new Type[]{type};
        }

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

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

    Converter<ResponseBody, ?> gsonConverter = factory
            .responseBodyConverter(wrappedType, annotations, retrofit);

    return new WrappedResponseBodyConverter(gsonConverter);
}

@Override
public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                      Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    Logger.e(getClass(), "requestBodyConverter");
    return factory
            .requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit);
}

}

```java
public class WrappedResponseBodyConverter<T>
        implements Converter<ResponseBody, T> {

    private Converter<ResponseBody, WrappedResponse<T>> converter;

    public WrappedResponseBodyConverter(Converter<ResponseBody,
            WrappedResponse<T>> converter) {
        Logger.e(getClass(), "WrappedResponseBodyConverter constructor");
        this.converter = converter;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {
        WrappedResponse<T> response = converter.convert(value);

        if (response.getStatus().equalsIgnoreCase(APIStatus.OK)) {
            return response.getResponse();
        }

        // RxJava will call onError with this exception
        throw new WrappedError(response.getRuntimeError());
    }

}
public class WrappedResponse<T> {

    private String status;
    private RuntimeError runtimeError;
    private T response;

    public String getStatus() {
        return status;
    }

    public RuntimeError getRuntimeError() {
        return runtimeError;
    }

    public T getResponse() {
        return response;
    }

}
public class WrappedError extends IOException {

    private final RuntimeError runtimeError;

    public WrappedError(RuntimeError runtimeError) {
        super();
        this.runtimeError = runtimeError;
    }

    public RuntimeError getRuntimeError() {
        return runtimeError;
    }
}

For the second structure I added the second converter factory .addConverterFactory(GsonConverterFactory.create(GsonProvider.gson)) to Retrofit.Builder:

 Retrofit.Builder retrofitBuilder =
                new Retrofit.Builder()
                        .baseUrl(BuildConfig.URL_BASE)
                        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                        .addConverterFactory(new UnwrapConverterFactory(GsonConverterFactory.create(GsonProvider.gson)))
                       .addConverterFactory(GsonConverterFactory.create(GsonProvider.gson));

But it does not get called. The response always processed by the first added converter factory.

The question is:

How can I tell UnwrapConverterFactory (first added converter factory) if it cannot handle the response then let the second converter factory to handle it?

P.S. I know that I should return null from the first added converter factory if it cannot handle the response, but how can I determine that the first added converter factory cannot handle the response?

Most helpful comment

APIVersion.class == annotation.annotationType() can be annotation instanceof APIVersion.
Instead of passing in your delegating Factory, you can use retrofit.nextRequestBodyConverter and retroift.nextResponseBodyConverter to get the delegate Converters.

The annotation usage looks like it works, yeah.
There are samples demonstrating this behavior.

All 4 comments

Check the type and annotations to see if the Factory can create the appropriate converter.
For example, maybe only Foo types are wrapped, so return null for non-Foo types.
Or, if some Foo types are wrapped, and some are not, create an annotation for the ones that are and check in the Factory similarly.

If you can boil this down to a clear example, StackOverflow with the retrofit tag may be able to help.

NightlyNexus, thanks for the tips. Used annotations to solve the problem. Annotate methods with custom annotation and check it in Converter.Factory. But I am not sure whether my solution is right. Is it okay to use annotations this way?

Here is my solution:

 @APIVersion(version = APIVersions.VERSION_2)
 @GET(BuildConfig.URL_BASE + "test")
 Observable<Test> testMethod();
@Target(METHOD)
@Retention(RUNTIME)
public @interface APIVersion {

    int version() default APIVersions.VERSION_1;

}
public class APIVersions {

     /* VERSION_1 - wrapped objects in json:
    {
        "status" : "ok",
        "response" : {
            ...
        }
    }
     */
    public static final int VERSION_1 = 1;

    /*
    {
        ...
    }
     */
    public static final int VERSION_2 = 2;

}
public class UnwrapConverterFactory extends Converter.Factory {

    private GsonConverterFactory factory;

    public UnwrapConverterFactory(GsonConverterFactory factory) {
        this.factory = factory;
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(final Type type,
                                                            Annotation[] annotations, Retrofit retrofit) {
        if (!canHandle(annotations)) {
            return null;
        }

        Type wrappedType = new ParameterizedType() {
            @Override
            public Type[] getActualTypeArguments() {
                // -> WrappedResponse<type>
                return new Type[]{type};
            }

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

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

        Converter<ResponseBody, ?> gsonConverter = factory
                .responseBodyConverter(wrappedType, annotations, retrofit);

        return new WrappedResponseBodyConverter(gsonConverter);
    }

    private boolean canHandle(Annotation[] annotations) {
        for (Annotation annotation : annotations) {

            if (APIVersion.class == annotation.annotationType()) {
                if (((APIVersion) annotation).version() == APIVersions.VERSION_2) {
                    return false;
                }

                break;
            }
        }

        return true;
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                          Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        Logger.e(getClass(), "requestBodyConverter");
        return factory
                .requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit);
    }

}

APIVersion.class == annotation.annotationType() can be annotation instanceof APIVersion.
Instead of passing in your delegating Factory, you can use retrofit.nextRequestBodyConverter and retroift.nextResponseBodyConverter to get the delegate Converters.

The annotation usage looks like it works, yeah.
There are samples demonstrating this behavior.

Thank you very much.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

attaullahpro picture attaullahpro  路  3Comments

ramonmoraes8080 picture ramonmoraes8080  路  3Comments

vkislicins picture vkislicins  路  3Comments

Liberuman picture Liberuman  路  3Comments

chriskessel picture chriskessel  路  3Comments