Andrew Andrew - 5 months ago 350
Android Question

Custom xml converter with Retrofit 2.0.2 is never called

I have to use retrofit 2.0.2 with xml api response. But my custom xml converter is never called.

Playing around with this I found out:


  • if I use Volley to parse the same response, the same custom xml converter IS called;

  • if I apply GsonConverterFactory to my RestClient and parse json response, my custom JsonAdapter (@JsonAdapter(SomeAdapter.class)) IS called.



Anyone, how make my simple xml converter to be called? Am I doing something wrong, or retrofit 2.0.2 somehow doesn't support simple xml converter.

My java class where I parse response:

import org.simpleframework.xml.Element;
import org.simpleframework.xml.convert.Convert;

public class PassengerResponse {
@Element
@Convert(value = SomeConverter.class)
private String id;
}


Custom xml converter that is never called:

import org.simpleframework.xml.convert.Converter;
import org.simpleframework.xml.stream.InputNode;
import org.simpleframework.xml.stream.OutputNode;

public class SomeConverter implements Converter<String> {
@Override
public String read(InputNode node) throws Exception {
return null;
}

@Override
public void write(OutputNode node, String value) throws Exception {
}
}


My retrofit RestClient:

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.simplexml.SimpleXmlConverterFactory;

public class RestClient2 {
private UserApiJSON userPassengerApi;
private static final int TIMEOUT = 120000;
private static RestClient2 INSTANCE;

public static RestClient2 getInstance() {
if (INSTANCE == null) {
INSTANCE = new RestClient2();
}
return INSTANCE;
}

private RestClient2() {

HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

OkHttpClient okHttpClient = new OkHttpClient();

Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(SimpleXmlConverterFactory.create())
.client(okHttpClient.newBuilder().connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.addInterceptor(loggingInterceptor)
.build())
.build();

userPassengerApi = retrofit.create(UserApiJSON.class);
}

public UserApiJSON getUserPassengerApi() {
return userPassengerApi;
}
}


Solution

I created my custom Converter.Factory. There in the method responseBodyConverter I applied AnnotationStrategy to my simple xml Serializer. After that my custom converter is called.

Changes in retrofit builder:

...
.addConverterFactory(CustomConverterFactory.create())
.addConverterFactory(SimpleXmlConverterFactory.create())


Note: order is important. If I switch factories places, CustomConvertedFactory won't be called. Why? Watch Jake Wharton presentation.

Custom converter factory:

public class CustomConverterFactory extends Converter.Factory {

public static CustomConverterFactory create() {
return new CustomConverterFactory();
}

private CustomConverterFactory() {
}

@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
if (PassengerResponse.class.equals(type)) {
return new Converter<ResponseBody, PassengerResponse>() {
@Override
public PassengerResponse convert(ResponseBody body) throws IOException {
InputStream inputStream = body.byteStream();
PassengerResponse passengerResponse = null;

//must be added to apply custom converter annotations
Strategy strategy = new AnnotationStrategy();
Serializer serializer = new Persister(strategy);
try {
passengerResponse = serializer.read(PassengerResponse.class, inputStream);
} catch (Exception e) {
e.printStackTrace();
}
return passengerResponse;
}
};
}
return null;
}
}

Answer

To use your custom converter you have create custom Converter.Factory. And than add it to the retrofit using method addConverterFactory(). Below working example:

public class StringConverterFactory extends Converter.Factory {

public static StringConverterFactory create() {
    return new StringConverterFactory();
}

@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
    if (String.class.equals(type)) {
        return new Converter<ResponseBody, String>() {
            @Override
            public String convert(ResponseBody value) throws IOException {
                return value.string();
            }
        };
    }
    return null;
}
}

And than add it retrofit

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(SimpleXmlConverterFactory.create())
        .addConverterFactory(StringConverterFactory.create())
        .client(okHttpClient.newBuilder().connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
                .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
                .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
                .addInterceptor(loggingInterceptor)
                .build())
        .build();

In Retrofit 2 allows multiple converters. There is video by Jake Wharton who talks about Retrofit 2 and it features like a multiple converters.

Inside Retrofit class there is a method nextRequestBodyConverter which returns converter for appropriate Type

Comments