Android:dynamically pass model class to retrofit c

2020-01-30 08:30发布

In retrofit to map json response to pojo usually we do this

@POST
Call<User> getDataFromServer(@Url String url, @Body HashMap<String,Object> hashMap);

ApiCalls api = retrofit.create(ApiCalls.class);
    Call<User> call = api.getDataFromServer(StringConstants.URL,hashMap);
    call.enqueue(new Callback<User>() {
         //Response and failure callbacks
    }

where User is my Pojo class. But for every other request i need to make different pojo and write same code for retrofit call.I want to make a single method for calling api and pass the respective pojo class to retrofit call. like this

ApiCalls api = retrofit.create(ApiCalls.class);
Call<*ClassPassed*> call = api.getDataFromServer(StringConstants.URL,hashMap);
call.enqueue(new Callback<*ClassPassed*>() {
     //Response and failure callbacks
}

so now i can any pojo class to single method and get the response.This is just to avoid rewriting the same code again and again.is this possible

Update To elaborate more:

Suppose I need to make two requests. First one is to get userDetails and the other is patientDetails.So i have to create two model classes User and Patient. So in retrofit api i'll be having something like this

@POST
Call<User> getDataFromServer(@Url String url, @Body HashMap<String,Object> hashMap);

@POST
Call<Patient> getDataFromServer(@Url String url, @Body HashMap<String,Object> hashMap);

and in my FragmentUser and FragmentPatient class i'll be doing this

  ApiCalls api = retrofit.create(ApiCalls.class);
Call<User> call = api.getDataFromServer(StringConstants.URL,hashMap);
call.enqueue(new Callback<User>() {
     //Response and failure callbacks
}

ApiCalls api = retrofit.create(ApiCalls.class);
Call<Patient> call = api.getDataFromServer(StringConstants.URL,hashMap);
call.enqueue(new Callback<Patient>() {
     //Response and failure callbacks
}

but here the code is repaeting just beacuse of different pojo classes.I need to repeat the same code in every other fragments for different requests. So i need to make a generic method where it can accept any pojo class and then from fragment i'll be just passing the pojo to be mapped.

10条回答
戒情不戒烟
2楼-- · 2020-01-30 09:09

Do it like this :

    @POST
    Call<Object> getDataFromServer(@Url String url, @Body HashMap<String,Object> hashMap);

    ApiCalls api = retrofit.create(ApiCalls.class);
        Call<Object> call = api.getDataFromServer(StringConstants.URL,hashMap);
        call.enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<Object> call, Response<Object> response) {
            YourModel modelObject = (YourModel) response.body();
            }

            @Override
            public void onFailure(Call<Object> call, Throwable t) {

            }
        }
查看更多
forever°为你锁心
3楼-- · 2020-01-30 09:13

Use standard generics, with a little bit of hacking around

Define your interface like this

public interface ApiCalls<T> {
    @POST
    Call<T> getResult getDataFromServer(@Url String url, @Body HashMap<String,Object> hashMap);
}

and call for creating api client use a helper method

class HelperMethods {
    @SuppressWarnings("unchecked")
    private static <T> ApiCalls<T> getClient() {
        return retrofit.create((Class<ApiCalls<T>>)(Class<?>)ApiCalls.class);
    }
}

ApiCalls<User> api = HelperMethods.getClient();

But despite of the fact how many times it has been said here, I am gonna say it again... Don't do this .. You are giving up the whole type safety and contract validation that Retrofit is offering .. That is actually the most exciting thing about it..

查看更多
放我归山
4楼-- · 2020-01-30 09:15

In Order to generalize what you want, you can simply serialize your POJO, and then you can just pass your POJO to the method as is. when you serialize with Objects it basically converts it to string, which are later converted to one big Json String, which are easier to transfer and manipulate.

A quick example would be:

example POJO implementing the serialization, here you should make sure the strings in the Map<String,Object> correspond to what the server is expecting to get, and this method should be different in each POJO:

public class YourPojo implements ObjectSerializer
{
  private static final long serialVersionUID = -1481977114734318563L;

  private String itemName;
  private int itemId;

  @Override
  public Map<String, Object> objectSerialize()
  {
   Map<String, Object> map = new HashMap<>();
   map.put("itemid", itemId); // server will be looking for "itemid"
   map.put("itemname", itemName); // server will be looking for "itemname"
   }

   //other stuff you need....
 }

The serialization interface (so you can implement it across other POJOs)

public interface ObjectSerializer extends Serializable
{
  public Map<String, Object> objectSerialize();
}

And a Json parser you shoul probably have anyways:

public class JsonParser
{
  public static JSONObject serializeToJsonString(Map<String, Object> jsonParams)
  {
    Gson gson = new Gson();
    String json = gson.toJson(jsonParams);
    JSONObject object;
    try
    {
        object = new JSONObject(json);
    }
    catch (Exception e)
    {
        object = new JSONObject(jsonParams);
    }
    return (object);
 }
}

And last, your API defintion:

@POST("users/createitem")
Call<ResponseBody> someCall(@Body RequestBody params);

And method, which should sit in a general class that manages your requests:

public void someMethodName(YourPojo item, final CustomEventListener<String> listener)
{
    JSONObject object = JsonParser.serializeToJsonString(item.objectSerialize());
    RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8";), object.toString());
    Call<ResponseBody> requestCall = serviceCaller.someCall(body);

    requestCall.enqueue(new Callback<ResponseBody>()
    {
        @Override
        public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> rawResponse)
        {
            try
            {
                String response = rawResponse.body().string();
                //do what you want with this string
                listener.getResult(response);
            }
            catch (Exception e)
            {
             e.printStackTrace();                    
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable throwable)
        {

        }
    });
    }

I return the response through a listener, that's one example of what you can do depending on your response.

Hope this helps!

查看更多
小情绪 Triste *
5楼-- · 2020-01-30 09:24

First Create Interface:

public interface
ItemAPI {

    @GET("setup.php")
    Call<SetupDetails> getSetup(@Query("site_id") int id,@Query("ino") String ii);

    @GET("setup.php")
    void setMy(@Query("site_id") int id, Callback<List<SetupDetails>> sd);
    }

Now create class:

public class Apiclient {

    private static final String BASE_URL ="http://www.YOURURL.COM/";
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {

        OkHttpClient.Builder httpClient2 = new OkHttpClient.Builder();
        httpClient2.addNetworkInterceptor(new Interceptor() {

            @Override
            public Response intercept(Chain chain) throws IOException {
                Request.Builder builder = chain.request().newBuilder();
                builder.addHeader("site_id","1");
                return chain.proceed(builder.build());
            }
        });
        OkHttpClient client = httpClient2.build();

        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(client)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }

Now in any activity just use:

ItemAPI itemAPI = Apiclient.getClient().create(ItemAPI.class);
     Call<AllProducts> call=itemAPI.getSetup(1,count);
     call.enqueue(new Callback<AllProducts>() {
            @Override
            public void onResponse(Call<AllProducts> call, Response<AllProducts> response) {
                try {
                    if (response.body().getItem().size()>0){

                    }

                }catch (Exception e){
                    e.printStackTrace();
                }
            }

            @Override
            public void onFailedAfterRetry(Throwable t) {

            }
        });
查看更多
太酷不给撩
6楼-- · 2020-01-30 09:24

I use following approach: First I have implemented custom Call

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

    public ProxyConvertCall2(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 ProxyConvertCall2<>(innerCall,converter);
    }

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

It wrappes Call<Tin> and converts it's result to <Tout> by converter.

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

For your service you must create service interface, that return JsonObject for single object and JsonArray for arrays

public interface ApiCalls {
    @POST
    Call<JsonObject> getDataFromServer(@Url String url, @Body HashMap<String,Object> hashMap);

    @POST
    Call<JsonArray> getArrayFromServer(@Url String url, @Body HashMap<String,Object> hashMap);
}

Then wrap it with generic class, with converters from JsonElement to any Type <T>:

public class ApiCallsGeneric<T> {
    Converter<JsonObject,T> fromJsonObject;
    Converter<JsonArray,List<T>> fromJsonArray;
    ApiCalls service;

    public ApiCallsGeneric(Class<T> classOfT, ApiCalls 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<T> getDataFromServer(String url, HashMap<String,Object> hashMap){
        return new ProxyConvertCall<>(service.getDataFromServer(url, hashMap), fromJsonObject);
     }

    public Call<List<T>> getArrayFromServer(String url, HashMap<String,Object> hashMap){ 
        return new ProxyConvertCall<>(service.getArrayFromServer(url, hashMap), fromJsonArray);
     }
}

GenericListType is ParaterizedType. It is used for passing type parameter to gson for 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;
    }

}

Then you can instantiate ApiCallsGeneric with type you want.

ApiCallsGeneric<User> userService= new ApiCallsGeneric<User>(User.class, retrofit.create(ApiCalls.class));
Call<User> call = userService.getDataFromServer(StringConstants.URL,hashMap);
call.enqueue(new Callback<User>() {
         //Response and failure callbacks
    }
查看更多
倾城 Initia
7楼-- · 2020-01-30 09:28

You can build main pojo like this

public class BaseResponse<T>
{
    @SerializedName("Ack")
    @Expose
    private String ack;

    @SerializedName("Message")
    @Expose
    private String message;

    @SerializedName("Data")
    @Expose
    private T data;

    /**
     *
     * @return
     * The ack
     */
    public String getAck() {
        return ack;
    }

    /**
     *
     * @param ack
     * The Ack
     */
    public void setAck(String ack) {
        this.ack = ack;
    }

    /**
     *
     * @return
     * The message
     */
    public String getMessage() {
        return message;
    }

    /**
     *
     * @param message
     * The Message
     */
    public void setMessage(String message) {
        this.message = message;
    }


    /**
     *
     * @return
     * The data
     */
    public T getData() {
        return data;
    }

    /**
     *
     * @param data
     * The Data
     */
    public void setData(T data) {
        this.data = data;
    }
}

And call like this

 Call<BaseResponse<SetupDetails>> getSetup(@Query("site_id") int id,@Query("ino") String ii);
查看更多
登录 后发表回答