I am working with an API that responds in two ways:
{
"status":"ok",
"response":{
...
}
}
{
"status" : "error",
"runtimeError" : {
...
}
}
{
...
}
{
"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:
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?
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.
Most helpful comment
APIVersion.class == annotation.annotationType()can beannotation instanceof APIVersion.Instead of passing in your delegating Factory, you can use
retrofit.nextRequestBodyConverterandretroift.nextResponseBodyConverterto get the delegate Converters.The annotation usage looks like it works, yeah.
There are samples demonstrating this behavior.