I am creating a generic API layer for Retrofit
Here is my service class:
public interface ApiService {
@POST("api/authenticate")
Call<Class> postData(@Body Class postBody);
}
public void postRequest(String actionUrl,GenericModelClass postBodyModel){
mApiService.postData(postBodyModel.getClass()).enqueue(new Callback<Class>() {
@Override
public void onResponse(Call<Class> call, Response<Class> response) {
response.getClass().getComponentType();
Log.d("TFFF", response.toString());
}
@Override
public void onFailure(Call<Class> call, Throwable t) {
Log.d("TFFF", t.toString());
}
});
}
But this one gives me:
java.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: a2a.rnd.com.a2ahttplibrary.retrofit.model.User. Forgot to register a type adapter?
I want to get the User
type from the generic type but I am getting this exception.
You're doing it in a way that does not make sense, and this is why you're getting:
java.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: a2a.rnd.com.a2ahttplibrary.retrofit.model.User. Forgot to register a type adapter?
Your service does not specify a type parameter. Class
handles quite another purpose: it's an object that represents a class loaded by JVM. Serializing and deserializing Class
instances makes really tiny sense if any, and that's why Gson does not provide it. All you want is generic methods. There are myriad articles for this subject over Internet.
Next, Retrofit does not work with method type parameters to simplify the type analysis under the hood dramatically. That's fine.
@GET("/")
<T> Call<T> get();
This won't work. How would you pass necessary type information data then? The only way to pass that info I can think of is introducing a wrapper to hold both value and its type (or type token to simplify Gson).
final class GenericBody<T> {
final T body;
final TypeToken<T> typeToken;
GenericBody(final T body, final TypeToken<T> typeToken) {
this.body = body;
this.typeToken = typeToken;
}
}
Then an example service might be declared as follows:
interface IGenericService {
@POST("/")
Call<Void> post(@Body @SuppressWarnings("rawtypes") GenericBody genericBody);
}
Here, the Call
is declared to return nothing, and genericBody
is intentionally made raw-typed to let it pass Retrofit validation.
Next, the Gson part.
final class GenericBodyTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory genericBodyTypeAdapterFactory = new GenericBodyTypeAdapterFactory();
private GenericBodyTypeAdapterFactory() {
}
static TypeAdapterFactory getGenericBodyTypeAdapterFactory() {
return genericBodyTypeAdapterFactory;
}
@Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !GenericBody.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
final TypeAdapter<GenericBody<T>> genericBodyTypeAdapter = new TypeAdapter<GenericBody<T>>() {
@Override
public void write(final JsonWriter out, final GenericBody<T> value)
throws IOException {
final T body = value.body;
final TypeAdapter<T> typeAdapter = gson.getDelegateAdapter(GenericBodyTypeAdapterFactory.this, value.typeToken);
typeAdapter.write(out, body);
}
@Override
public GenericBody<T> read(final JsonReader in) {
throw new UnsupportedOperationException();
}
};
@SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) genericBodyTypeAdapter;
return typeAdapter;
}
}
What it does it is:
GenericBody
instances;<T>
by the bound type token;No read is implemented.
Example of use (full of mocks (staticResponse(applicationJsonMediaType, "OK")
) that can be easily translated to your code):
private static final TypeToken<List<String>> stringListTypeToken = new TypeToken<List<String>>() {
};
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getGenericBodyTypeAdapterFactory())
.create();
private static final OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(staticResponse(applicationJsonMediaType, "OK"))
.build();
private static final Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://whatever")
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
private static final IGenericService genericService = retrofit.create(IGenericService.class);
public static void main(final String... args)
throws IOException {
final GenericBody<List<String>> body = new GenericBody<>(asList("foo", "bar", "baz"), stringListTypeToken);
genericService.post(body).execute();
}
This would write ["foo","bar","baz"]
to the output stream respecting properly configured Gson (de)serialization strategies.