MultiConverterFactory in Retrofit: Handling Both JSON and XML Responses with Dynamic Annotation-Based Conversion in Android
Anil kr Maurya
Posted on October 17, 2024
In many Android applications, especially those interacting with different APIs, the need to handle both JSON and XML formats for requests and responses arises. Retrofit, a popular library for handling HTTP requests, typically allows either JSON or XML conversion through its GsonConverterFactory
or SimpleXmlConverterFactory
, but not both at the same time.
To overcome this limitation, you can create a MultiConverterFactory
, which dynamically switches between JSON and XML formats based on custom annotations (@RequestFormat
and @ResponseFormat
) applied at the method level in Retrofit. This allows each Retrofit API method to define its preferred format for requests and responses.
Enum ConverterFormat
:
public enum ConverterFormat {
JSON,
XML
}
The ConverterFormat
enum defines the possible formats (JSON and XML) supported by the converter factory. It is used in the annotations to specify whether a method should use JSON or XML conversion.
Custom Annotations: RequestFormat
and ResponseFormat
These annotations allow you to specify which format (JSON or XML) a given method should use for requests and responses.
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target(METHOD)
@Retention(RUNTIME)
public @interface ResponseFormat {
ConverterFormat value() default ConverterFormat.XML;
}
@Target(METHOD)
@Retention(RUNTIME)
public @interface RequestFormat {
ConverterFormat value() default ConverterFormat.JSON;
}
-
@RequestFormat
: Defines the format for the request body (default is JSON). -
@ResponseFormat
: Defines the format for the response body (default is XML).
MultiConverterFactory Class
The MultiConverterFactory
class is a custom Retrofit converter that dynamically switches between JSON and XML based on the annotations.
import com.google.gson.GsonBuilder;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.simplexml.SimpleXmlConverterFactory;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
public class MultiConverterFactory extends Converter.Factory {
private final Converter.Factory xmlFactory;
private final Converter.Factory jsonFactory;
// Constructor initializes both XML and JSON converter factories
public MultiConverterFactory() {
this.xmlFactory = SimpleXmlConverterFactory.createNonStrict(); // For XML
this.jsonFactory = GsonConverterFactory.create(); // For JSON
}
// Handles request body conversion based on @RequestFormat annotation
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations,
Annotation[] methodAnnotations, Retrofit retrofit) {
// Check for the RequestFormat annotation on the method
for (Annotation annotation : methodAnnotations) {
if (annotation instanceof RequestFormat) {
RequestFormat requestFormat = (RequestFormat) annotation;
// Switch based on the format specified (JSON or XML)
switch (requestFormat.value()) {
case JSON:
return jsonFactory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit);
case XML:
return xmlFactory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit);
}
}
}
// Default to JSON if no annotation is found
return jsonFactory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit);
}
// Handles response body conversion based on @ResponseFormat annotation
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
// Check for the ResponseFormat annotation on the method
for (Annotation annotation : annotations) {
if (annotation instanceof ResponseFormat) {
ResponseFormat responseFormat = (ResponseFormat) annotation;
// Switch based on the format specified (JSON or XML)
switch (responseFormat.value()) {
case JSON:
return jsonFactory.responseBodyConverter(type, annotations, retrofit);
case XML:
return xmlFactory.responseBodyConverter(type, annotations, retrofit);
}
}
}
// Default to JSON if no annotation is found
return jsonFactory.responseBodyConverter(type, annotations, retrofit);
}
}
-
xmlFactory
andjsonFactory
: These are the two factory instances used to handle XML and JSON conversions respectively. -
requestBodyConverter()
: Looks for the@RequestFormat
annotation and switches between the JSON and XML converters accordingly. -
responseBodyConverter()
: Similar to the request converter but for handling responses, based on the@ResponseFormat
annotation.
Usage Example
Here’s how you can apply the @RequestFormat
and @ResponseFormat
annotations in a Retrofit interface:
public interface ApiService {
@RequestFormat(ConverterFormat.JSON)
@ResponseFormat(ConverterFormat.XML)
@POST("uploadData")
Call<ResponseBody> uploadData(@Body DataModel data);
@RequestFormat(ConverterFormat.XML)
@ResponseFormat(ConverterFormat.JSON)
@GET("fetchData")
Call<DataModel> fetchData();
}
- The
uploadData()
method will send data in JSON format and expects the response in XML. - The
fetchData()
method will send the request in XML format and expects the response in JSON.
import android.content.Context;
import com.netcosports.ntlm.NTLMAuthenticator;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.simplexml.SimpleXmlConverterFactory;
/**
* Created by Anil on 10/14/2017.
* */
public class ApiClient {
// Main Link
private static final String BASE_URL = "";
private static Retrofit retrofit = null;
private static final ApiClient ourInstance = new ApiClient();
private Context context;
public static ApiClient getInstance() {
return ourInstance;
}
public ApiService getApiService(Context context) {
this.context=context;
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.addConverterFactory(new MultiConverterFactory()).build();
}
return retrofit.create(ApiService.class);
}
}
Required Dependencies
Make sure to add the necessary dependencies in your build.gradle
file for Retrofit, Gson, and SimpleXML support:
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-simplexml:2.9.0'
}
Why Use MultiConverterFactory
?
- Flexibility: This approach allows your application to handle multiple content types (JSON and XML) for different API methods.
- Dynamic Conversion: You can switch between JSON and XML conversion dynamically based on the method using simple annotations.
- Cleaner Code: Using custom annotations makes your API interface more readable and eliminates the need for handling conversion logic manually in each method.
Conclusion
By implementing the MultiConverterFactory
with custom annotations @RequestFormat
and @ResponseFormat
, you can easily manage APIs that deal with both JSON and XML formats. This approach makes your Retrofit implementation more versatile and adaptable to various API formats without sacrificing code readability or maintainability.
Posted on October 17, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024