Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Incompatible types inferred type does not conform to equality constraint(s)

So I have a model Model.

public class Model { .... } 

This has two subclasses:

public class SubmodelA extend Model { .... }

and

public class SubmodelB extend Model { .... }

These three are wrapped under Data class.

public class ApiData<T extends Model> {

    public T data;

}

My general response wrapper looks like this:

public class ApiResponse<DATA> {

    DATA data;
}

The "dummy" api operation remains the same:

public interface Endpoints {

    Call<ApiResponse<ApiData>> getData();
}

I have an implementation of retrofit2.Callback to handle the responses:

public class ApiCallbackProxy<T> implements retrofit2.Callback<T> {

    public interface ApiResultListener<RESPONSE_TYPE> {
        void onResult(RESPONSE_TYPE response, ApiError error);
    }

    private ApiResultListener<T> mListener;

    private ApiCallbackProxy(ApiResultListener<T> listener) {
        mListener = listener;
    }

    @Override
    public void onResponse(Call<T> call, Response<T> response) {

    }

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

    }

    public static <T> ApiCallbackProxy<T> with(ApiResultListener<T> callback) {
        return new ApiCallbackProxy<>(callback);
    }
}

The ApiClient

public class ApiClient {

    public Endpoints mRetrofit;

    public ApiClient() {
       Retrofit retrofit = new Retrofit.Builder().build();
       mRetrofit = retrofit.create(Endpoints.class);
    }

    public <U extends Model> void getData(ApiResultListener<ApiResponse<ApiData<U>>> callback) {
       //Compiler hits here
       mRetrofit.getData().enqueue(ApiCallbackProxy.with(callback));
    }
}

Compiler hits at ApiCallbackProxy.with(callback) with this error:

enter image description here

So I want depending on where this API call is used in the app to return a different subclass of model or the model itself.

ie.

public static void main (String[] args) {
    ApiClient apiClient = new ApiClient();
    apiClient.getData(listener2);
}


public static final ApiResultListener<ApiResponse<Data<SubmodelA>>> listener = (response, error) -> {};

public static final ApiResultListener<ApiResponse<Data<Model>>> listener2 = (response, error) -> {};

public static final ApiResultListener<ApiResponse<Data<SubmodelB>>> listener3 = (response, error) -> {};
like image 863
Manos Avatar asked Jun 01 '17 19:06

Manos


1 Answers

The ApiClient class has a listener that expects ApiData<U> in the response.

The problem is, there is no U. You have an Endpoint, and the endpoint has no generic types, and it returns just ApiData with no concrete type selected for the generic.

This is a case of generics gone wrong. The usual idea would be to make Endpoint generic:

public interface Endpoints<U> {
    Call<ApiResponse<ApiData<U>>> getData();
}

However, what does the Retrofit do? It turns HTTP API into Java interface. And looking at the most simple example at the github repo of Retrofit, it seems pretty clear to me that you are supposed to put interfaces that access some real HTTP endpoint. It is not some abstract GET.

So you'd rather give it a concrete type than make it generic. Do something like:

public interface Endpoints {
    Call<ApiResponse<ApiData<Model>>> getData();
}

I expect Retrofit to deserialize the data in the response to your Model. So, having a concrete class rather than an unset generic type variable is crucial to the successful deserialization. However, you can only use it with listeners being either of the following:

ApiResultListener<ApiResponse<Data<Model>>>
ApiResultListener<ApiResponse<Data<? super Model>>>

Also, in the older part of the question, the ApiResponse<Payload> part where the generic type variable looks like it was Payload class, now that is quite devious :)

like image 198
Vlasec Avatar answered Nov 18 '22 11:11

Vlasec