Keywords: Retrofit 2.0 | CallAdapter | SimpleXML | REST Client | Java Networking
Abstract: This article provides an in-depth analysis of the CallAdapter mechanism in Retrofit 2.0, focusing on the common exception "Unable to create call adapter for class example.Simple" encountered when using SimpleXML converters. It explains how Retrofit 2.0 adapts Call<T> to other return types via CallAdapters and discusses proper configuration of service interfaces to return business objects instead of Call wrappers. By comparing error examples with correct implementations, the article offers two solutions: returning Call<Simple> directly or creating a custom CallAdapter.Factory. Additionally, it references supplementary insights from other answers regarding Kotlin coroutines, emphasizing the importance of suspend functions in asynchronous programming. Through code examples and mechanistic diagrams, the article helps readers fully grasp Retrofit's adapter architecture and its practical applications in development.
Overview of CallAdapter Mechanism in Retrofit 2.0
Retrofit 2.0 introduces the CallAdapter mechanism, a key distinction from earlier versions. In Retrofit 1.x, service interface methods could directly return business objects (e.g., Simple), with the framework handling synchronous or asynchronous calls automatically. However, Retrofit 2.0 adopts a more flexible design: all network requests are encapsulated in Call<T> objects, and CallAdapter is responsible for adapting Call<T> to other return types. This design allows developers to support libraries like RxJava or Guava by adding different CallAdapters, but it also requires service interface methods to return either Call<T> or a type convertible via an adapter.
Problem Analysis: Why CallAdapter for Simple Cannot Be Created
In the user's example, the service interface is defined as:
public interface SimpleService {
@GET("/simple/{id}")
Simple getSimple(@Path("id") String id);
}Here, the getSimple method attempts to return a Simple object directly. When Retrofit 2.0 tries to create a proxy for this method, it searches for a CallAdapter that can adapt Call<Simple> to Simple. By default, Retrofit only provides DefaultCallAdapter, which supports returning Call<T> types exclusively. Thus, the framework fails to find a suitable adapter, throwing an IllegalArgumentException: Unable to create call adapter for class example.Simple exception.
Solution 1: Return Call<Simple>
The most straightforward solution is to modify the service interface to return Call<Simple>:
public interface SimpleService {
@GET("/simple/{id}")
Call<Simple> getSimple(@Path("id") String id);
}This allows Retrofit to use DefaultCallAdapter for proper request handling. Calls can be made synchronously via call.execute() or asynchronously via call.enqueue(). Although this adds a Call wrapper layer, it aligns with Retrofit 2.0's design philosophy, offering greater flexibility and error-handling capabilities.
Solution 2: Custom CallAdapter.Factory
If developers insist on returning business objects directly, they can implement a custom CallAdapter.Factory. For example, create an adapter that converts Call<Simple> to Simple:
public class SimpleCallAdapterFactory extends CallAdapter.Factory {
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (returnType == Simple.class) {
return new CallAdapter<Simple, Simple>() {
@Override
public Type responseType() {
return Simple.class;
}
@Override
public Simple adapt(Call<Simple> call) {
try {
return call.execute().body();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
}
return null;
}
}Then add this factory to the Retrofit builder:
final Retrofit rest = new Retrofit.Builder()
.addConverterFactory(SimpleXmlConverterFactory.create())
.addCallAdapterFactory(new SimpleCallAdapterFactory())
.baseUrl(endpoint)
.build();This allows the service interface to maintain the Simple return type, with the custom adapter handling synchronous execution internally. However, note that this approach may hide network errors and is not suitable for asynchronous scenarios.
Additional Notes: Kotlin Coroutines and RxJava Adapters
Referencing other answers, in Kotlin with coroutines, similar issues may arise from not marking service functions as suspend. For example:
interface ApiService {
@GET("/my-get-request")
suspend fun myGetRequest(): Response<String>
}Retrofit supports coroutines via specialized CallAdapters (e.g., CoroutineCallAdapterFactory), ensuring suspend functions work correctly. Similarly, RxJavaCallAdapterFactory is only applicable when returning Observable<T> or similar Rx types, not for direct Simple object returns. In the user's update, the error message Could not locate call adapter for class simple.Simple. Tried: * retrofit.RxJavaCallAdapterFactory * retrofit.DefaultCallAdapter$1 indicates the framework attempted these adapters, but none matched the Simple return type.
Conclusion and Best Practices
The CallAdapter mechanism in Retrofit 2.0 offers powerful extensibility but requires explicit return types from developers. For most scenarios, returning Call<T> is the best practice, as it maintains framework consistency and error-handling capabilities. If special needs arise, custom CallAdapter.Factory is a viable option, but careful handling of exceptions and asynchronous issues is essential. In practice, it is recommended to choose methods based on project requirements and refer to official documentation for the latest adapter support. By understanding how CallAdapters work, developers can leverage Retrofit more effectively to build robust REST clients.