2017-07-27 2 views
0

J'utilise Retrofit2 dans mon application et cela fonctionne très bien, mais j'essaie d'utiliser une seule interface pour toutes sortes de demandes de méthode GET en transmettant un modèle dynamique. Quand je l'essayer en utilisant le code suivant, il donne l'erreur lors de l'exécutionImpossible de rendre l'interface Retrofit2 générique pour tous les modèles

public interface LoadDataServiceTest<T> { 
     @GET 
     Call<T> getModel(@Url String url, @QueryMap Map<String, String> options); 
    } 

Modèle:

public class ModelTest<T> { 
    @SerializedName("status") 
    private String status; 
    @SerializedName("message") 
    private String message; 
    @SerializedName("users") 
    private T data; 

    public String getStatus() { 
     return status; 
    } 

    public String getMessage() { 
     return message; 
    } 

    public T getData() { 
     return data; 
    } 
} 

Mais quand je crée un service comme suit, j'obtiens l'erreur. Comment puis-je le résoudre et quelle est la meilleure approche pour atteindre cet objectif.

LoadDataServiceTest<ModelTest<JsonArray>> service = retrofit.create((Class<LoadDataServiceTest<ModelTest<JsonArray>>>) (Class<?>) APIs.LoadDataServiceTest.class); 
     Map<String, String> parameters = new HashMap<>(); 
     parameters.put("user_id",userId); 
     Call<ModelTest<JsonArray>> call = service.getModel(APIs.GET_USERS, parameters); 

Erreur:

FATAL EXCEPTION: main Process: com.iu.colgatepalmolive, PID: 16808 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.iu.colgatepalmolive/com.iu.hfl_ccp.LoginActivity}: java.lang.IllegalArgumentException: Method return type must not include a type variable or wildcard: retrofit2.Call for method LoadDataServiceTest.getModel at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2666) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727) at android.app.ActivityThread.-wrap12(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1478) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6121) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779) Caused by: java.lang.IllegalArgumentException: Method return type must not include a type variable or wildcard: retrofit2.Call for method LoadDataServiceTest.getModel at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:720) at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:711) at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:224) at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:160) at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:166) at retrofit2.Retrofit$1.invoke(Retrofit.java:145) at java.lang.reflect.Proxy.invoke(Proxy.java:813) at $Proxy5.getModel(Unknown Source)

+1

Cochez cette case [https://github.com/square/retrofit/issues/2012] – santalu

+0

En bref, ce n'est pas possible. Merci pour référence. –

Répondre

0

J'utilise suivant l'approche. Au début, je l'ai mis en œuvre sur mesure Appel

public class ProxyConvertCall<Tin,Tout> implements Call<Tout> { 
    Converter<Tin,Tout> converter; 
    Call<Tin> innerCall; 

    public ProxyConvertCall(Call<Tin> jcall, Converter<Tin,Tout> converter){ 
     this.innerCall = jcall; 
     this.converter = converter; 
     } 

    @Override 
    public Response<Tout> execute() throws IOException { 
     Response<Tin> response = innerCall.execute(); 
     if (response.isSuccessful()){ 
      return Response.success(converter.Convert(response.body()),response.raw()); 
     } 
     else return Response.error(response.code(), response.errorBody()); 
    } 

    @Override 
    public void enqueue(final Callback<Tout> callback) { 
     final Call<Tout> self = this; 
     this.innerCall.enqueue(new Callback<Tin>() { 
      @Override 
      public void onResponse(Call<Tin> call, Response<Tin> response) { 
       if (response.isSuccessful()){ 
        callback.onResponse(self, Response.success(converter.Convert(response.body()), response.raw())); 
       } 
       else callback.onResponse(self, Response.error(response.code(), response.errorBody())); 
      } 
      @Override 
      public void onFailure(Call<Tin> call, Throwable t) { 
       callback.onFailure(self,t); 
      } 
     }); 

    } 

    @Override 
    public boolean isExecuted() { 
     return innerCall.isExecuted(); 
    } 

    @Override 
    public void cancel() { 
     innerCall.cancel(); 

    } 

    @Override 
    public boolean isCanceled() { 
     return innerCall.isCanceled(); 
    } 

    @Override 
    public Call<Tout> clone() { 
     return new ProxyConvertCall<>(innerCall,converter); 
    } 

    @Override 
    public Request request() { 
     return innerCall.request(); 
    } 
} 

Il adapte Call<Tin> à l'aide d'un convertisseur Call<Tou>.

@FunctionalInterface 
public interface Converter<Tin, Tout> { 
    public Tout Convert(Tin in); 
} 

Pour votre service, vous devez créer une interface de service, que le retour JsonObject pour objet unique et JsonArray pour les tableaux

public interface LoadDataServiceTest { 
    @GET 
    public Call<JsonObject> getModelTest(@Url String url, @QueryMap Map<String, String> options); 

    @GET 
    public Call<JsonObject> getModel(@Url String url, @QueryMap Map<String, String> options); 

    @GET 
    public Call<JsonArray> getModels(@Url String url, @QueryMap Map<String, String> options); 


} 

wrap et avec classe générique, avec des convertisseurs de JsonElement à tout type <T> et ModelTest<T> :

public class LoadDataServiceTestGeneric<T> { 
    Converter<JsonObject,ModelTest<T>> fromJsonObjectToModelTest; 
    Converter<JsonObject,T> fromJsonObject; 
    Converter<JsonArray,List<T>> fromJsonArray; 
    LoadDataServiceTest service; 

    public LoadDataServiceTestGeneric(Class<T> classOfT, LoadDataServiceTest service){ 
     this.service = service; 
     Gson gson = new GsonBuilder().create(); 
     GenericListType<T> genericListTypeOfT = new GenericListType<T>(classOfT); 
     fromJsonObject = (t)->gson.fromJson(t,classOfT); 
     fromJsonArray =(t)->gson.fromJson(t,genericListTypeOfT); 
    } 
    public Call<ModelTest<T>> getModelTest(String url, Map<String, String> options){ 
     return new ProxyConvertCall<>(service.getModelTest(url, options), fromJsonObjectToModelTest); 
    } 

    public Call<T> getModel(String url, Map<String, String> options){ 
     return new ProxyConvertCall<>(service.getModel(url, options), fromJsonObject); 
    } 

    Call<List<T>> getModels(String url, Map<String, String> options){ 
     return new ProxyConvertCall<>(service.getModels(url, options), fromJsonArray); 
    } 

} 

GenericListType est ParameterizedType. Il est utilisé pour le paramètre de type de passage à gson pour List<T>

import java.lang.reflect.ParameterizedType; 
import java.lang.reflect.Type; 
import java.util.List; 

public class GenericListType<T> implements ParameterizedType { 

    private Type wrapped; 

    public GenericListType(Type wrapped) { 
     this.wrapped = wrapped; 
    } 

    public Type[] getActualTypeArguments() { 
     return new Type[] {wrapped}; 
    } 

    public Type getRawType() { 
     return List.class; 
    } 

    public Type getOwnerType() { 
     return null; 
    } 

} 

ModelTestType est ParameterizedType. Il est utilisé pour le paramètre de type de passage à gson pour ModelTestType<T>

public class ModelTestType implements ParameterizedType { 

     private Type wrapped; 

     public ModelTestType(Type wrapped) { 
      this.wrapped = wrapped; 
     } 

     public Type[] getActualTypeArguments() { 
      return new Type[] {wrapped}; 
     } 

     public Type getRawType() { 
      return ModelTest.class; 
     } 

     public Type getOwnerType() { 
      return null; 
     } 

    } 

Ensuite, vous pouvez instancier LoadDataServiceTestGeneric avec le type que vous voulez.