12

In my project (I am using Dagger 2, Retrofit 2 & OkHTTP, RxAndroid) i have 2 different API calls. From one I receive JSON, from other - XML. I studied this link and added 2 converters that i need to my Retrofit.Builder():

@Provides
@Singleton
public Gson providesGson() {
    return new GsonBuilder().create();
}

@Provides
@Singleton
public Retrofit providesRetrofit(@NonNull OkHttpClient okHttpClient, @NonNull Gson gson) {
    return new Retrofit.Builder()
            .baseUrl(ConstantsManager.BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(SimpleXmlConverterFactory.create())
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build();
}

But then, when I receive JSON (XML gets converted correctly), I get the following:

D/OkHttp: {"date":"20.11.2016","bank":"PB","baseCurrency":980,"baseCurrencyLit":"UAH","exchangeRate":[{"baseCurrency":"UAH","currency":"AUD","saleRateNB":19.4452740,"purchaseRateNB":19.4452740},{"baseCurrency":"UAH","currency":"CAD","saleRateNB":19.4047320,"purchaseRateNB":19.4047320},{"baseCurrency":"UAH","currency":"CZK","saleRateNB":1.0322170,"purchaseRateNB":1.0322170,"saleRate":1.0800000,"purchaseRate":0.9800000},{"baseCurrency":"UAH","currency":"DKK","saleRateNB":3.7519280,"purchaseRateNB":3.7519280},{"baseCurrency":"UAH","currency":"HUF","saleRateNB":0.0902556,"purchaseRateNB":0.0902556},{"baseCurrency":"UAH","currency":"ILS","saleRateNB":6.7524710,"purchaseRateNB":6.7524710,"saleRate":7.0000000,"purchaseRate":6.3000000},{"baseCurrency":"UAH","currency":"JPY","saleRateNB":0.2384005,"purchaseRateNB":0.2384005,"saleRate":0.2500000,"purchaseRate":0.2200000},{"baseCurrency":"UAH","currency":"LVL","saleRateNB":0.2384005,"purchaseRateNB":0.2384005},{"baseCurrency":"UAH","currency":"LTL","saleRateNB":0.2384005,"purchaseRateNB":0.2384005},{"baseCurrency":"UAH","currency":"NOK","saleRateNB":3.0724120,"purchaseRateNB":3.0724120,"saleRate":3.2000000,"purchaseRate":2.9000000},{"baseCurrency":"UAH","currency":"SKK","saleRateNB":3.0724120,"purchaseRateNB":3.0724120},{"baseCurrency":"UAH","currency":"SEK","saleRateNB":2.8384710,"purchaseRateNB":2.8384710},{"baseCurrency":"UAH","currency":"CHF","saleRateNB":26.0049080,"purchaseRateNB":26.0049080,"saleRate":27.5000000,"purchaseRate":25.0000000},{"baseCurrency":"UAH","currency":"RUB","saleRateNB":0.4013400,"purchaseRateNB":0.4013400,"saleRate":0.4200000,"purchaseRate":0.4000000},{"baseCurrency":"UAH","currency":"GBP","saleRateNB":32.4460750,"purchaseRateNB":32.4460750,"saleRate":34.0000000,"purchaseRate":31.0000000},{"baseCurrency":"UAH","currency":"USD","saleRateNB":26.0534380,"purchaseRateNB":26.0534380,"saleRate":27.0000000,"purchaseRate":26.6000000},{"baseCurrency":"UAH","currency":"BYR","saleRateNB":26.0534380,"purchaseRateNB":26.0534380},{"baseCurrency":"UAH","currency":"EUR","saleRateNB":27.9214700,"purchaseRateNB":27.9214700,"saleRate":28.6000000,"purchaseRate":28.2000000},{"baseCurrency":"UAH","currency":"GEL","saleRateNB":10.5921530,"purchaseRateNB":10.5921530},{"baseCurrency":"UAH","currency":"PLZ","saleRateNB":6.2818280,"purchaseRateNB":6.2818280,"saleRate":6.6000000,"purchaseRate":5.9000000}]}
D/OkHttp: <-- END HTTP (2377-byte body)
E/DateCurrencyService: Error while loading data occurred!
                   java.lang.RuntimeException: org.xmlpull.v1.XmlPullParserException: Unexpected token (position:TEXT {"date":"20.11.2...@1:2378 in java.io.InputStreamReader@2ec8965) 
                       at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:44)
                       at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:23)
                       at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
                       at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
                       at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
                       at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
                       at rx.internal.operators.OperatorSubscribeOn$1$1$1.request(OperatorSubscribeOn.java:80)
                       at rx.Subscriber.setProducer(Subscriber.java:209)
                       at rx.Subscriber.setProducer(Subscriber.java:205)
                       at rx.Subscriber.setProducer(Subscriber.java:205)
                       at rx.internal.operators.OperatorSubscribeOn$1$1.setProducer(OperatorSubscribeOn.java:76)
                       at rx.Subscriber.setProducer(Subscriber.java:205)
                       at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
                       at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
                       at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50)
                       at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
                       at rx.Observable.unsafeSubscribe(Observable.java:8666)
                       at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
                       at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker$1.call(CachedThreadScheduler.java:220)
                       at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
                       at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
                       at java.util.concurrent.FutureTask.run(FutureTask.java:237)
                       at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
                       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
                       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
                       at java.lang.Thread.run(Thread.java:761)
                    Caused by: org.xmlpull.v1.XmlPullParserException: Unexpected token (position:TEXT {"date":"20.11.2...@1:2378 in java.io.InputStreamReader@2ec8965) 
                       at org.kxml2.io.KXmlParser.next(KXmlParser.java:432)
                       at org.kxml2.io.KXmlParser.next(KXmlParser.java:313)
                       at org.simpleframework.xml.stream.PullReader.read(PullReader.java:105)
                       at org.simpleframework.xml.stream.PullReader.next(PullReader.java:89)
                       at org.simpleframework.xml.stream.NodeReader.readElement(NodeReader.java:111)
                       at org.simpleframework.xml.stream.NodeReader.readRoot(NodeReader.java:85)
                       at org.simpleframework.xml.stream.NodeBuilder.read(NodeBuilder.java:84)
                       at org.simpleframework.xml.stream.NodeBuilder.read(NodeBuilder.java:71)
                       at org.simpleframework.xml.core.Persister.read(Persister.java:562)
                       at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:36)
                       at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:23) 
                       at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117) 
                       at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211) 
                       at retrofit2.OkHttpCall.execute(OkHttpCall.java:174) 
                       at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171) 
                       at rx.internal.operators.OperatorSubscribeOn$1$1$1.request(OperatorSubscribeOn.java:80) 
                       at rx.Subscriber.setProducer(Subscriber.java:209) 
                       at rx.Subscriber.setProducer(Subscriber.java:205) 
                       at rx.Subscriber.setProducer(Subscriber.java:205) 
                       at rx.internal.operators.OperatorSubscribeOn$1$1.setProducer(OperatorSubscribeOn.java:76) 
                       at rx.Subscriber.setProducer(Subscriber.java:205) 
                       at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152) 
                       at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138) 
                       at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50) 
                       at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) 
                       at rx.Observable.unsafeSubscribe(Observable.java:8666) 
                       at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94) 
                       at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker$1.call(CachedThreadScheduler.java:220) 
                       at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) 
                       at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428) 
                       at java.util.concurrent.FutureTask.run(FutureTask.java:237) 
                       at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272) 
                       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) 
                       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) 
                       at java.lang.Thread.run(Thread.java:761) 

As i understood what is going on there is that Simple XML Converter tries to convert the JSON response i get from second call (XML response from first call, as i said earlier, get converted correctly). What i need to do is to convert each response with appropriate converter (XML response with the Simple XML Converter and JSON response with the Gson Converter)

If i try to switch the order of adding converters i will get a similar error when receiving XML (because Gson converter tries to convert XML response, obviously):

D/OkHttp: <exchangerate><exchangerate ccy="EUR" ccy_name_ru="Евро                               " ccy_name_ua="Євро                               " ccy_name_en="Euro                               " base_ccy="UA" buy="27247312" unit="100.00000" date="2016.11.29" /><exchangerate ccy="RUR" ccy_name_ru="Российский рубль                   " ccy_name_ua="Росiйський рубль                   " ccy_name_en="Russian Rouble                     " base_ccy="UA" buy="39810" unit="10.00000" date="2016.11.29" /><exchangerate ccy="USD" ccy_name_ru="Доллар США                         " ccy_name_ua="Долар США                          " ccy_name_en="US Dollar                          " base_ccy="UA" buy="25724426" unit="100.00000" date="2016.11.29" /></exchangerate>
D/OkHttp: <-- END HTTP (799-byte body)
E/SyncService: Error while loading data occurred!
           com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
               at com.google.gson.stream.JsonReader.syntaxError(JsonReader.java:1559)
               at com.google.gson.stream.JsonReader.checkLenient(JsonReader.java:1401)
               at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:593)
               at com.google.gson.stream.JsonReader.peek(JsonReader.java:425)
               at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:205)
               at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
               at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:25)
               at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
               at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
               at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
               at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
               at rx.internal.operators.OperatorSubscribeOn$1$1$1.request(OperatorSubscribeOn.java:80)
               at rx.Subscriber.setProducer(Subscriber.java:209)
               at rx.Subscriber.setProducer(Subscriber.java:205)
               at rx.Subscriber.setProducer(Subscriber.java:205)
               at rx.internal.operators.OperatorSubscribeOn$1$1.setProducer(OperatorSubscribeOn.java:76)
               at rx.Subscriber.setProducer(Subscriber.java:205)
               at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
               at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
               at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:50)
               at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
               at rx.Observable.unsafeSubscribe(Observable.java:8666)
               at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
               at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker$1.call(CachedThreadScheduler.java:220)
               at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
               at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
               at java.util.concurrent.FutureTask.run(FutureTask.java:237)
               at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
               at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
               at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
               at java.lang.Thread.run(Thread.java:761)

What can I do to make each converter convert the appropriate response?

UPD: As was suggested by Marcin Jedynak i did the following:

1) Added custom Converter class:

public class RetrofitUniversalConverter extends Converter.Factory {

    private final Converter.Factory xml;
    private final Converter.Factory json;

    @Inject
    public RetrofitUniversalConverter(@NonNull Gson gson) {
        xml = SimpleXmlConverterFactory.create();
        json = GsonConverterFactory.create(gson);
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {

        for (Annotation annotation : annotations) {

            if (annotation.getClass() == Xml.class) {
                return xml.responseBodyConverter(type, annotations, retrofit);
            }

            if (annotation.getClass() == Json.class) {
                return json.responseBodyConverter(type, annotations, retrofit);
            }

        }

        return null;
    }
}

2) Rewrited my Retrofit module:

@Provides
@Singleton
public Gson providesGson() {
    return new GsonBuilder().create();
}

@Provides
@Singleton
public Retrofit providesRetrofit(@NonNull OkHttpClient okHttpClient,
                                 @NonNull RetrofitUniversalConverter converter) {
    return new Retrofit.Builder()
            .baseUrl(ConstantsManager.BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(converter)
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build();
}

3) Then, I added annotations in my API interface:

public interface PrivatbankApi {

    @GET @Xml
    Observable<CurrentRates> loadCurrentRates(@NonNull @Url String url);

    @GET("exchange_rates") @Json
    Observable<DateRates> loadDateRates(@NonNull @Query("json") Boolean json, @NonNull @Query("date") String date);

}

But then i get exception:

E/AndroidRuntime: FATAL EXCEPTION: main
              Process: com.vedmedenko.exchangerates, PID: 5432
              java.lang.RuntimeException: Unable to start service com.vedmedenko.exchangerates.core.services.SyncService@68cf714 with Intent { flg=0x4 cmp=com.vedmedenko.exchangerates/.core.services.SyncService (has extras) }: java.lang.IllegalArgumentException: Unable to create converter for class com.vedmedenko.exchangerates.core.rest.models.current.CurrentRates
                  for method PrivatbankApi.loadCurrentRates
                  at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3343)
                  at android.app.ActivityThread.-wrap21(ActivityThread.java)
                  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1582)
                  at android.os.Handler.dispatchMessage(Handler.java:102)
                  at android.os.Looper.loop(Looper.java:154)
                  at android.app.ActivityThread.main(ActivityThread.java:6119)
                  at java.lang.reflect.Method.invoke(Native Method)
                  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
                  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
               Caused by: java.lang.IllegalArgumentException: Unable to create converter for class com.vedmedenko.exchangerates.core.rest.models.current.CurrentRates
                  for method PrivatbankApi.loadCurrentRates
                  at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:720)
                  at retrofit2.ServiceMethod$Builder.createResponseConverter(ServiceMethod.java:706)
                  at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:167)
                  at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:166)
                  at retrofit2.Retrofit$1.invoke(Retrofit.java:145)
                  at java.lang.reflect.Proxy.invoke(Proxy.java:813)
                  at $Proxy0.loadCurrentRates(Unknown Source)
                  at com.vedmedenko.exchangerates.core.DataManager.loadCurrentRates(DataManager.java:29)
                  at com.vedmedenko.exchangerates.core.services.SyncService.onStartCommand(SyncService.java:81)
                  at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3326)
                    ... 8 more
               Caused by: java.lang.IllegalArgumentException: Could not locate ResponseBody converter for class com.vedmedenko.exchangerates.core.rest.models.current.CurrentRates.
                Tried:
                 * retrofit2.BuiltInConverters
                 * com.vedmedenko.exchangerates.core.rest.converters.RetrofitUniversalConverter
                  at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:346)
                  at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:308)
                  at retrofit2.ServiceMethod$Builder.createResponseConverter(ServiceMethod.java:704)
                    ... 16 more
Mark Amery
  • 143,130
  • 81
  • 406
  • 459
oleg.v
  • 1,065
  • 3
  • 11
  • 20
  • What is the JSON / XML data response? Why not go with the suggestion from the error message - use setLenient. see http://stackoverflow.com/a/11488385/4191629 – maciekjanusz Nov 26 '16 at 23:19
  • Well, as i understood what is going on there is that Simple XML Converter tries to convert the JSON response i get from second call (XML response from first call gets converted correctly). What i need to do is convert each response with appropriate converter (XML response with Simple XML Converter and JSON response with Gson Converter). – oleg.v Nov 26 '16 at 23:24
  • What is CurrentRates class? – EpicPandaForce Nov 27 '16 at 12:55
  • Updated post, check it out please. – oleg.v Nov 27 '16 at 13:06

3 Answers3

19

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
Marcin Jedynak
  • 3,697
  • 2
  • 20
  • 19
  • I've edited post. Can you please check this out? I still get the error. For some reason Retrofit could not locate converter for XML response. – oleg.v Nov 27 '16 at 12:31
  • Ok, what i have figured out is that somehow my custom converter not sees my _Xml_ and _Json_ annotations. From output i see that only _Get_ annotation is processed. But what am i missing, why **RetrofitUniversalConverter** is not recognizing my _Xml_ or _Json_ annotations? – oleg.v Nov 27 '16 at 13:40
  • Try to add @Retention(RetentionPolicy.RUNTIME) to your annotations, I see Jake doesn't mention it, but annotations by default are discarded during runtime. – Marcin Jedynak Nov 27 '16 at 13:47
  • Added _@Retention(RetentionPolicy.RUNTIME)_, but i still get the same error. Anyways, i came to the solution and will update my post now. – oleg.v Nov 27 '16 at 13:52
  • Ok, but does it see the annotations now or it still doesn't? – Marcin Jedynak Nov 27 '16 at 13:56
  • The annotation _@Json_ or _@Xml_ was not getting passed in _Annotation[] annotations_ array, even with _@Retention(RetentionPolicy.RUNTIME)_. – oleg.v Nov 27 '16 at 14:01
  • I found the problem. `annotation.getClass() == Json.class` and its Xml equivalent should be replaced with `annotation.annotationType() == Json.class`. I updated the answer. – Marcin Jedynak Nov 27 '16 at 15:37
4

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);
vir us
  • 9,920
  • 6
  • 57
  • 66
1

For kotlin users it's much simpler

@MustBeDocumented
@Target(
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
annotation class XmlResponse

@MustBeDocumented
@Target(
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
annotation class JsonResponse

class MultipleConverterFactory(private val factories: Map<Class<*>, Converter.Factory>) : Converter.Factory() {

    override fun responseBodyConverter(
        type: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): Converter<ResponseBody, *>? {
        return annotations.mapNotNull { factories[it.annotationClass.javaObjectType] }
            .getOrNull(0)
            ?.responseBodyConverter(type, annotations, retrofit)
    }

    class Builder {

        private val factories = hashMapOf<Class<*>, Converter.Factory>()

        fun setXmlConverterFactory(converterFactory: Converter.Factory): Builder {
            factories[XmlResponse::class.java] = converterFactory
            return this
        }

        fun setJsonConverterFactory(converterFactory: Converter.Factory): Builder {
            factories[JsonResponse::class.java] = converterFactory
            return this
        }

        @Suppress("unused")
        fun addCustomConverterFactory(
            annotation: Class<out Annotation>,
            converterFactory: Converter.Factory
        ): Builder {
            factories[annotation] = converterFactory
            return this
        }

        fun build(): MultipleConverterFactory {
            return MultipleConverterFactory(factories)
        }
    }
}

And usage

Retrofit.Builder()
    // ...
    .addConverterFactory(
        MultipleConverterFactory.Builder()
            .setXmlConverterFactory(TikXmlConverterFactory.create(instance()))
            .setJsonConverterFactory(GsonConverterFactory.create(instance()))
            .build()
    )
    .build()

// some API endpoint
@XmlResponse
@GET
suspend fun getXml(
    @Url url: String
): MyModel
Vlad
  • 7,997
  • 3
  • 56
  • 43