Reputation: 4693
Is it possible to using retrofit in a manner that uses generic type?
for example something like this:
public interface RetroInterface<T> {
@GET("content/{id}")
T getById(@Path("id") int id);
}
I've read that Retrofit uses the method signature to determine the return Type at runtime, in that case is it even possible to have generic interface such as above?
Upvotes: 4
Views: 3590
Reputation: 540
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:
checks if it can handle GenericBody instances; resolves appropriate type adapters for the by the bound type token; writes the generic body value to the output. 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.
Upvotes: 1
Reputation: 41
Yes I think it's possible but be carefull retrofit return some Call
So you can create an interface with Call<T>
like method except
But Have you really need to create a template for a service ? Because in you get annotations you ask to server one specific ressource so you known the type of response
Upvotes: 1