DEV Community

Anil kr Maurya
Anil kr Maurya

Posted on

MultiConverterFactory in Retrofit: Handling Both JSON and XML Responses with Dynamic Annotation-Based Conversion in Android

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
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode
  • @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);
    }
}
Enter fullscreen mode Exit fullscreen mode
  • xmlFactory and jsonFactory: 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();
}
Enter fullscreen mode Exit fullscreen mode
  • 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);
    }


}
Enter fullscreen mode Exit fullscreen mode

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'
}
Enter fullscreen mode Exit fullscreen mode

Why Use MultiConverterFactory?

  1. Flexibility: This approach allows your application to handle multiple content types (JSON and XML) for different API methods.
  2. Dynamic Conversion: You can switch between JSON and XML conversion dynamically based on the method using simple annotations.
  3. 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.

Top comments (0)