arash moeen
arash moeen

Reputation: 4693

Android using Retrofit with generic type

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

Answers (2)

Abdul Ahad
Abdul Ahad

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

Romain de-coster
Romain de-coster

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

Related Questions