I'm using Retrofit2 and RxJava2CallAdapterFactory
.
The API I consume returns status code always as 200 and for success and response JSON string the JSON structure is entirely different. Since the status code is always 200 the onResponse() method is called always. Hence, I'm not able to extract error msgs from the JSON in the error condition.
Solution 1:
I use ScalarsConverterFactory
to get response String and manually use Gson to parse the response .
How to get response as String using retrofit without using GSON or any other library in android
Problem with this solution: I'm planning to use RxJava2CallAdapterFactory for that the retrofit method should return DataModel Class.
I need to find the best solution for this problem, in way I can keep returning the data model classes from Retrofit method & somehow I identify the error condition from response (identify the response JSON does not match the data model) and then parse the error JSON into a data model.
Retrofit Client
public static Retrofit getClient(String url) {
if (apiClient == null) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(interceptor).build();
apiClient = new Retrofit.Builder()
.baseUrl(url)
/*addCallAdapterFactory for RX Recyclerviews*/
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
/* add ScalarsConverterFactory to get json string as response */
// .addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
// .addConverterFactory(GsonConverterFactory.create(gson))
.client(httpClient)
.build();
}
return apiClient;
}
Method
public static void getLoginAPIResponse(String username, String password, String sourceId, String uuid, final HttpCallback httpCallback) {
baseUrl = AppPreference.getParam(UiUtils.getContext(), SPConstants.BASE_URL, "").toString();
ApiInterface apiService =
ApiClient.getClient(baseUrl).create(ApiInterface.class);
Call<LoginBean> call = apiService.getLoginResponse(queryParams);
call.enqueue(new Callback<LoginBean>() {
@Override
public void onResponse(Call<LoginBean> call, Response<LoginBean> response) {
if (response.body().isObjectNull()) {
httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_FAILURE, 0, null);
return;
}
httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_SUCCESS, response.code(), response.body());
}
@Override
public void onFailure(Call<LoginBean> call, Throwable t) {
// Log error here since request failed
httpCallback.resultCallback(APIConstants.API_APP_VERIFICATION, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_FAILURE, 0, t);
t.printStackTrace();
}
});
}
Interface
@GET("App/login")
Call<LoginBean> getLoginResponse(@QueryMap Map<String, String> queryMap);
PS : The API cannot change for now, as some other applications are consuming it.
Gson parser does not return a null object instance for me to understand that there is json structure and datamodel mismatch.
RestAdapter is deprecated in Retrofit 2
I'm looking for the best approach to resolve this , preferably avoid manually json parsing and take most advantage of retrofit and RX adapters.
EDIT
Response code 200 hence
response.isSuccessful() == true
response.body() != null
is also true as Gson never creates a null instance or throws any exception if there is mismatch of JSON structureresponse.errorBody() == null
at all times as response sent as input stream from the server.if (response.isSuccessful() && response.body() != null) { //control always here as status code 200 for error condition also }else if(response.errorBody()!=null){ //control never reaches here }
EDIT 2
SOLUTION
The solution is based on anstaendig answer
- I have created a base generic class to further this answer.
- Since I have multiple apis and data models I have to create deserilizers for each
BASE API BEAN
public class BaseApiBean<T> {
@Nullable
private T responseBean;
@Nullable
private ErrorBean errorBean;
public BaseApiBean(T responseBean, ErrorBean errorBean) {
this.responseBean = responseBean;
this.errorBean = errorBean;
}
public T getResponseBean() {
return responseBean;
}
public void setResponseBean(T responseBean) {
this.responseBean = responseBean;
}
public ErrorBean getErrorBean() {
return errorBean;
}
public void setErrorBean(ErrorBean errorBean) {
this.errorBean = errorBean;
}
}
BASE DESERIALIZER
public abstract class BaseDeserializer implements JsonDeserializer<BaseApiBean> {
@Override
public BaseApiBean deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
// Get JsonObject
final JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("result")) {
/* {"result":"404"}*/
ErrorBean errorMessage = new Gson().fromJson(jsonObject, ErrorBean.class);
return getResponseBean(errorMessage);
} else {
return getResponseBean(jsonObject);
}
}
public abstract BaseApiBean getResponseBean(ErrorBean errorBean);
public abstract BaseApiBean getResponseBean(JsonObject jsonObject);
}
Custom Deserializer for each API
public class LoginDeserializer extends BaseDeserializer {
@Override
public BaseApiBean getResponseBean(ErrorBean errorBean) {
return new LoginResponse(null, errorBean);
}
@Override
public BaseApiBean getResponseBean(JsonObject jsonObject) {
LoginBean loginBean = (new Gson().fromJson(jsonObject, LoginBean.class));
return new LoginResponse(loginBean, null);
}
}
CUSTOM RESPONSE BEAN
public class LoginResponse extends BaseApiBean<LoginBean> {
public LoginResponse(LoginBean responseBean, ErrorBean errorBean) {
super(responseBean, errorBean);
}
}
CLIENT
public class ApiClient {
private static Retrofit apiClient = null;
private static Retrofit apiClientForFeedBack = null;
private static LoginDeserializer loginDeserializer = new LoginDeserializer();
private static AppVerificationDeserializer appVerificationDeserializer = new AppVerificationDeserializer();
public static Retrofit getClient(String url) {
if (apiClient == null) {
GsonBuilder gsonBuilder=new GsonBuilder();
gsonBuilder.registerTypeAdapter(LoginResponse.class,
loginDeserializer);
gsonBuilder.registerTypeAdapter(AppVerificationResponse.class,
appVerificationDeserializer);
Gson gson= gsonBuilder.create();
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(interceptor)
.retryOnConnectionFailure(true)
.connectTimeout(15, TimeUnit.SECONDS)
.build();
apiClient = new Retrofit.Builder()
.baseUrl(url)
/*addCallAdapterFactory for RX Recyclerviews*/
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
/* add ScalarsConverterFactory to get json string as response */
// .addConverterFactory(ScalarsConverterFactory.create())
// .addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(httpClient)
.build();
}
return apiClient;
}
HANDLE RESPONSE
public static void getLoginAPIResponse(String username, String password, String sourceId, String uuid, final HttpCallback httpCallback) {
baseUrl = AppPreference.getParam(getContext(), SPConstants.MT4_BASE_URL, "").toString();
ApiInterface apiService =
ApiClient.getClient(baseUrl).create(ApiInterface.class);
HashMap<String, String> queryParams = new HashMap<>();
queryParams.put(APIConstants.KEY_EMAIL, sourceId + username.toLowerCase());
queryParams.put(APIConstants.KEY_PASSWORD, Utils.encodePwd(password));
Call<LoginResponse> call = apiService.getLoginResponse(queryParams);
call.enqueue(new Callback<LoginResponse>() {
@Override
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
if (response.body().getResponseBean()==null) {
httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_FAILURE, 0, response.body().getErrorBean());
return;
}
httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_SUCCESS, response.code(), response.body().getResponseBean());
}
@Override
public void onFailure(Call<LoginResponse> call, Throwable t) {
// Log error here since request failed
httpCallback.resultCallback(APIConstants.API_APP_VERIFICATION, HttpCallback.REQUEST_TYPE_GET,
HttpCallback.RETURN_TYPE_FAILURE, 0, t);
t.printStackTrace();
}
});
}