Android Retrofit 2 multiple converters (Gson & SimpleXML) error
Check this presentation by Jake Wharton, where he solves exactly the problem you describe (and presents many other useful tricks).
In short he suggests creating annotations representing your expected data format (for example Json
and Xml
in your case) and annotate your API calls accordingly. Then you define your custom ConverterFactory
where you delegate to either GsonConverterFactory
or SimpleXmlConverterFactory
depending on the annotation encountered.
However, you need add two things to the Jake's solution.
First do not forget to annotate your annotations with @Retention(RetentionPolicy.RUNTIME)
otherwise they won't be kept during the runtime:
@Retention(RetentionPolicy.RUNTIME)
public @interface Json {}
Second, the annotations you receive in your responseBodyConverter
method are not actually your annotations. They are proxies created by the system to your annotations. Therefore you need to replace the conditions:
annotation.getClass() == Json.class
annotation.getClass() == Xml.class
with:
annotation.annotationType() == Json.class
annotation.annotationType() == Xml.class
Thanks to @Marcin and the reference.
Below is the code from the mentioned presentation by Jake Warton adjusted according to Marcin's comments (and added Json factory as default if no annotation was found):
public class AnnotatedConverterFactory extends Converter.Factory {
@Retention(RetentionPolicy.RUNTIME)
public @interface Json {}
@Retention(RetentionPolicy.RUNTIME)
public @interface Xml {}
final Map<Class<?>, Converter.Factory> mFactoryMap;
public AnnotatedConverterFactory(Map<Class<?>, Converter.Factory> factoryMap) {
mFactoryMap = factoryMap;
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
for(Annotation annotation : annotations){
Converter.Factory factory = mFactoryMap.get(annotation.annotationType());
if(factory != null){
return factory.responseBodyConverter(type, annotations, retrofit);
}
}
//try to default to json in case no annotation on current method was found
Converter.Factory jsonFactory = mFactoryMap.get(Json.class);
if(jsonFactory != null){
return jsonFactory.responseBodyConverter(type, annotations, retrofit);
}
return null;
}
static class Builder {
Map<Class<?>, Converter.Factory> mFactoryMap = new LinkedHashMap<>();
Builder add(Class<? extends Annotation> factoryType, Converter.Factory factory){
if(factoryType == null) throw new NullPointerException("factoryType is null");
if(factory == null) throw new NullPointerException("factory is null");
mFactoryMap.put(factoryType, factory);
return this;
}
public AnnotatedConverterFactory build(){
return new AnnotatedConverterFactory(mFactoryMap);
}
}
}
Usage:
interface RetrofitService{
@POST
@AnnotatedConverterFactory.Xml
Call<Envelope> someApiMethodWithXmlRespoonse(@Url String url, @Body Envelope envelope);
@POST
@AnnotatedConverterFactory.Json
Call<Envelope> someApiMethodWithJsonRespoonse(@Url String url, @Body Model model);
}
Instantiate retrofit:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(mOkHttpClient)
.addConverterFactory(new AnnotatedConverterFactory.Builder()
.add(AnnotatedConverterFactory.Xml.class, SimpleXmlConverterFactory.createNonStrict())
.add(AnnotatedConverterFactory.Json.class, GsonConverterFactory.create(mGson))
.build()
)
mRetrofitService = retrofit.create(RetrofitService.class);