rodent_la
rodent_la

Reputation: 1395

Generic type in retrofit for the custom call adapter

Retrofit async request is callback with 2 methods onResponse() and onFailure().

I don't want to always override these 2 methods and handle for the error case.

So when i want to through the google GithubBrowserSample of ApiResponse which wrapping the retrofit response body and convert the error as below:

public class ApiResponse<T> {

    public final int code;
    @Nullable
    public final T body;
    @Nullable
    public final String errorMessage;

    public ApiResponse(Throwable error) {
        code = -1;
        body = null;
        if (error instanceof IOException) {
            errorMessage = "No network error";
        }
        else {
            errorMessage = error.getMessage();
        }
    }

    public ApiResponse(Response<T> response) {
        code = response.code();
        if (response.isSuccessful()) {
            body = response.body();
            errorMessage = null;
        }
        else {
            String message = null;
            if (response.errorBody() != null) {
                try {
                    message = response.errorBody().string();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (message == null || message.trim().length() == 0) {
                message = response.message();
            }
            errorMessage = message;
            body = null;
        }
    }

    public boolean isSuccessful() {
        return code >= 200 && code < 300;
    }
}

Ialso want to use the Gson convertor to convert the retrofit response and then wrap it with ApiResponse.

If i use like

Call<ApiResponse<Result>> requestCall = webClient.request1(xxx,xxx);
requestCall.enqueue(new Callback<ApiResponse<Result>> {});

It seems not work. The json response data can't be parsed into the Result object.

So i think about writing my own custom Call Adapter referencing retrofit sample to replace with Retrofit Call. But i still have the problem in converting generic type.

public class MyCallAdapterFactory extends CallAdapter.Factory {
    @Nullable
    @Override
    public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {

        if (getRawType(returnType) != MyCall.class) {
            return null;
        }

        Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
        Class<?> rawObservableType = getRawType(observableType);
        if (rawObservableType != ApiResponse.class) {
            throw new IllegalArgumentException("type must be a resource");
        }
        if (! (observableType instanceof ParameterizedType)) {
            throw new IllegalArgumentException("resource must be parameterized");
        }
        Type bodyType = getParameterUpperBound(0, (ParameterizedType) observableType);
        Executor executor = retrofit.callbackExecutor();
        return new MyCallAdapter<>(bodyType, executor);
    }
}

public class MyCallAdapter<T> implements CallAdapter<T, MyCall<T>> {

    private final Type responseType;
    private final Executor callbackExecutor;

    public MyCallAdapter(Type responseType, Executor callbackExecutor) {
        this.responseType = responseType;
        this.callbackExecutor = callbackExecutor;
    }

    @Override
    public Type responseType() {
        return null;
    }

    @Override
    public MyCall<T> adapt(Call<T> call) {
        return new MyCallImpl<>(call, callbackExecutor);
    }
}

public class MyCallImpl<T> implements MyCall<T> {
    private final Call<T> call;

    private final Executor callbackExecutor;

    MyCallImpl(Call<T> call, Executor callbackExecutor) {
        this.call = call;
        this.callbackExecutor = callbackExecutor;
    }

    @Override
    public void enqueue(MyCallback<T> callback) {
        call.enqueue(new Callback<T>() {
            @Override
            public void onResponse(Call<T> call, Response<T> response) {
                /* This is the problem. it will seems wrap to ApiResponse<ApiResponse<Result>>> because T is <ApiResponse<Result>>.
                */
                callback.onResult(new ApiResponse<>(response));
            }

            @Override
            public void onFailure(Call<T> call, Throwable t) {
                /** This one is also the issue. */
                callback.onResult(new ApiResponse<>(t));
            }
        });
    }

    @Override
    public void cancel() {
        call.cancel();
    }

    @Override
    public MyCall<T> clone() {
        return new MyCallImpl<>(call.clone(), callbackExecutor);
    }
}


public interface MyCallback<T> {

    void onResult(ApiResponse<T> response);

}

Above code has the problem in handling double parameterized generic type. I don't know how to handle it.

Also running this code is also crash

  Caused by: java.lang.NullPointerException: type == null
      at retrofit2.Utils.checkNotNull(Utils.java:286)
      at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:324)
      at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:313)
      at retrofit2.ServiceMethod$Builder.createResponseConverter(ServiceMethod.java:736)
      at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:169) 

Could someone help how to let MyCall<ApiResponse<Result>> to call enqueue with MyCallback<ApiResponse<Result>>? Result is the parsing of the json data content with Gson converter.

public class MyCallAdapter<T> implements CallAdapter<T, MyCall<ApiResponse<T>>> {

   public MyCall<ApiResponse<T>> adapt(Call<T> call) {
        /* This one will have the problem after changing MyCall<T> to MyCall<ApiResponse<T>>, Parameterized type mismatch.*/
        return new MyCallImpl<>(call, callbackExecutor);
    }
}

Can someone help me point out the issue?

Upvotes: 2

Views: 7118

Answers (1)

rodent_la
rodent_la

Reputation: 1395

Modify the MyCallAdapter, MyCallback and the MyCallImpl. @Rahul points out the response type and now everything work fine.

public class MyCallAdapter<T> implements CallAdapter<T, MyCall<ApiResponse<T>>> {

    private final Type responseType;
    private final Executor callbackExecutor;

    public MyCallAdapter(Type responseType, Executor callbackExecutor) {
        this.responseType = responseType;
        this.callbackExecutor = callbackExecutor;
    }

    @Override
    public Type responseType() {
        return responseType;
    }

    @Override
    public MyCall<ApiResponse<T>> adapt(Call<T> call) {
        return new MyCallImpl<>(call, callbackExecutor);
    }
}


    public interface MyCallback<T> {

        void onResult(T response);

    }


public class MyCallImpl<T> implements MyCall<ApiResponse<T>> {
    private final Call<T> call;

    private final Executor callbackExecutor;

    MyCallImpl(Call<T> call, Executor callbackExecutor) {
        this.call = call;
        this.callbackExecutor = callbackExecutor;
    }

    @Override
    public void enqueue(MyCallback<ApiResponse<T>> callback) {
        call.enqueue(new Callback<T>() {
            @Override
            public void onResponse(Call<T> call, Response<T> response) {
                callback.onResult(new ApiResponse<>(response));
            }

            @Override
            public void onFailure(Call<T> call, Throwable t) {
                callback.onResult(new ApiResponse<>(t));
            }
        });
    }

    @Override
    public void cancel() {
        call.cancel();
    }

    @Override
    public MyCall<ApiResponse<T>> clone() {
        return new MyCallImpl<>(call.clone(), callbackExecutor);
    }
}

This above is the correct implementation. Yeah.

Upvotes: 1

Related Questions