6

В моем проекте (я использую Dagger 2, Retrofit 2 & OkHTTP, RxAndroid) У меня есть 2 разных вызова API. От одного я получаю JSON, от другого - XML. Я изучал this link и добавил 2 конвертеры, что мне нужно для моей Retrofit.Builder():Android Retrofit 2 несколько конвертеров (Gson & SimpleXML) error

@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(); 
} 

Но тогда, когда я получаю JSON (XML получает правильно преобразованы), я получаю следующее:

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":"[email protected]:2378 in [email protected]) 
         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":"[email protected]:2378 in [email protected]) 
         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)  

Как я понял, что происходит, так это то, что Simple XML Converter пытается преобразовать ответ JSON, который я получаю от второго вызова (ответ XML от первого вызова, как я сказал ранее, преобразуется правильно). Мне нужно сделать, чтобы преобразовать каждый ответ с соответствующим конвертером (ответ XML с помощью простого XML-конвертера и ответ JSON с помощью Gson Converter)

Если я попытаюсь переключить порядок добавления конвертеров, то я получу аналогичную ошибку, если получение XML (потому что Gson преобразователь пытается преобразовать ответ XML, очевидно):

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) 

Что я могу сделать, чтобы каждый преобразователь преобразует соответствующий ответ?

UPD: Как было предложено Marcin Jedynak я сделал следующее:

1) Добавлен пользовательский конвертер класс:

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) переписан мой дооснащения модуль:

@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) Затем я добавил аннотации в интерфейсе API:

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); 

} 

Но я получаю исключение:

E/AndroidRuntime: FATAL EXCEPTION: main 
       Process: com.vedmedenko.exchangerates, PID: 5432 
       java.lang.RuntimeException: Unable to start service [email protected] 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 
+0

Что является ответом данных/XML JSON? Почему бы не пойти с предложением из сообщения об ошибке - используйте setLenient. см. http://stackoverflow.com/a/11488385/4191629 – maciekjanusz

+0

Ну, как я понял, что происходит, так это то, что Simple XML Converter пытается преобразовать ответ JSON, который я получаю от второго вызова (XML-ответ от первого вызова преобразуется правильно). Что мне нужно сделать, так это преобразовать каждый ответ с соответствующим конвертером (ответ XML с помощью простого XML-конвертера и ответа JSON с помощью Gson Converter). – Sleepwalker

+0

Что такое класс CurrentRates? – EpicPandaForce

ответ

9

Проверить this presentation Джейком Wharton, где он решает именно эту проблему, вы можете описать (и представляет множество других полезных трюков).

Вкратце он предлагает создать аннотации, представляющие ваш ожидаемый формат данных (например, Json и Xml в вашем случае) и соответствующим образом аннотировать ваши запросы API. Затем вы определяете свой пользовательский номер ConverterFactory, где вы делегируете либо GsonConverterFactory, либо SimpleXmlConverterFactory в зависимости от найденной аннотации.

Однако, вам нужно добавить две вещи к решению Джейка.

Во-первых, не забывайте комментировать ваши аннотаций с @Retention(RetentionPolicy.RUNTIME) в противном случае они не будут храниться во время выполнения:

@Retention(RetentionPolicy.RUNTIME) 
public @interface Json {} 

Во-вторых, аннотации, которые вы получаете в вашем методе responseBodyConverter фактически не являются аннотаций. Они являются прокси-серверами, созданными системой для ваших аннотаций.Поэтому вам необходимо заменить условие:

annotation.getClass() == Json.class 
annotation.getClass() == Xml.class 

с:

annotation.annotationType() == Json.class 
annotation.annotationType() == Xml.class 
+0

Я отредактировал сообщение. Можете ли вы проверить это? Я все еще получаю ошибку. По какой-то причине Retrofit не смог найти конвертер для ответа XML. – Sleepwalker

+0

Хорошо, я выяснил, что каким-то образом мой пользовательский конвертер не видит аннотации _Xml_ и _Json_. Из вывода я вижу, что обрабатывается только аннотация _Get_. Но что мне не хватает, почему ** RetrofitUniversalConverter ** не распознает аннотации _Xml_ или _Json_? – Sleepwalker

+0

Попробуйте добавить @Retention (RetentionPolicy.RUNTIME) в свои аннотации, я вижу, что Джейк не упоминает об этом, но аннотации по умолчанию отбрасываются во время выполнения. –