3

I want to interact with a RESTful webservice that responds only in JSON. Any successful response from the server has this syntax:

{
    "code": int code,
    "data": object or list of objects
}

while on error response:

{
    "code": int code,
    "error": string,
    "details": string
}

So I made two classes in my Android project like this (for GSON reflection):

public class ErrorEntity {
    private String details;
    private String error;
    private int    code;

    public ErrorEntity() {
        // Stub constructor
    }

    public String getDetails() {
        return details;
    }

    public String getError() {
        return error;
    }

    public int getCode() {
        return code;
    }
}

For a successful response I made a generic because I don't want to parse JSON data on overridden parseNetworkResponse:

public class SuccessfulEntity<T> {

    private T   data;
    private int code;

    public SuccessfulEntity() {
        // Stub content
    }

    public T getData() {
        return data;
    }

    public int getCode() {
        return code;
    }
}

Now, because my RESTful server requires some custom headers, I need to make a Request subclass, but I don't know from which class I need to inherit.
I saw this question: Send POST request with JSON data using Volley and though to do something like that.

Basically, I want to make a new class (VolleyRestClient) which has GET, POST, DELETE methods and API routings, and with this class make all requests I need to do.

Methods of this class need to make a new custom request and parse new objects response like SuccessfulEntity and ErrorEntity, and then parsing data in service/thread that make the VolleyRestClient call.

How can I do that?

Community
  • 1
  • 1
Dan.see
  • 88
  • 11
  • why not `Entity` which would have all props from `SuccessfulEntity` and `ErrorEntity` ... and make props which are not common in those classes optional ... and for error check you would use `entity.getError() != null` – Selvin Dec 14 '15 at 16:25
  • @Selvin and how do I get Class> to pass to custom request for GSON parsing? – Dan.see Dec 14 '15 at 16:57
  • in the same way as you want to do with SuccessfulEntity ... so obviously you should know the T type before creating custom request – Selvin Dec 14 '15 at 16:58
  • Resolved making 2 separate class, "SuccessfulObjEntity" and "SuccessfulListEntity" inherited from Entity class, but then data obj/list is already parsed by Gson, am I right? Do I just need to make a cast to the appropriate type? – Dan.see Dec 14 '15 at 17:17

1 Answers1

0

After a long fight with generics and type erasure, I finally did it.
So I'm posting this for whoever has the same issue like me and needs a solution without freaking out.

My ErrorEntity and my SuccessfulEntity are still the same, but I created a new interface called RepositoryListener, like this:

public interface RepositoryListener {
    public abstract void onErrorResponse(int code, String details);
    public abstract void onSuccessfulResponse(int code, Object obj);
    public abstract void onSuccessfulResponse2(int code, List<Object> obj);
}

Then I made a class, VolleyRestClient, like this:

public class VolleyRestClient extends RestClient {

    private final DefaultRetryPolicy mRetryPolicy;
    private final RequestQueue       mQueue;
    private final Gson               gson = new Gson();

    public VolleyRestClient(Context context) {
        // Default retry policy
        mRetryPolicy = new DefaultRetryPolicy(2000, 3, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
        mQueue       = Volley.newRequestQueue(context);
    }

    public RequestQueue getQueue() {
        // Method to push requests for image download
        return mQueue;
    }

    @Override
    public void GET(boolean obj, boolean needAuth, String url, Type type,
                    RepositoryListener listener) {
        // Choose which listener to construct
        Response.Listener<myResponse> mListener = obj ?
                // This uses objects
                makeSuccessfulListener(listener, type) :
                // This uses list of objects
                makeSuccessfulListener2(listener, type);

        myRequest mRequest =
                new myRequest(Request.Method.GET, needAuth, url,
                        mListener, makeErrorListener(listener));

        mRequest.setRetryPolicy(mRetryPolicy);
        mQueue.add(mRequest);
    }

    @Override
    public void POST(boolean needAuth, String url, String body, Type type, RepositoryListener listener) {
        myRequest mRequest = new myRequest(Request.Method.POST, needAuth, url, body,
                        makeSuccessfulListener(listener, type), makeErrorListener(listener));

        mRequest.setRetryPolicy(mRetryPolicy);
        mQueue.add(mRequest);
    }

    @Override
    public void DELETE(boolean needAuth, String url, Type type, RepositoryListener listener) {
        myRequest mRequest =
                new myRequest(Request.Method.DELETE, needAuth, url,
                        makeSuccessfulListener(listener, type), makeErrorListener(listener));

        mRequest.setRetryPolicy(mRetryPolicy);
        mQueue.add(mRequest);
    }

    private Response.Listener<myRequest> makeSuccessfulListener
            (final RepositoryListener listener, final Type type) {
        // TODO: test this method and implement lists
        if (listener == null) {
            return null;
        } else {
            return new Response.Listener<myRequest>() {
                @Override
                public void onResponse(myRequest response) {
                    SuccessfulEntity<Object> obj = gson.fromJson(response.getBody(), type);
                    listener.onSuccessfulResponse(response.getCode(), obj.getData());
                }
            };
        }
    }

    private Response.Listener<myRequest> makeSuccessfulListener2
            (final RepositoryListener listener, final Type type) {
        // TODO: test lists
        if (listener == null) {
            return null;
        } else {
            return new Response.Listener<myRequest>() {
                @Override
                public void onResponse(myReqyest response) {
                    SuccessfulEntity<List<Object>> obj = gson.fromJson(response.getBody(), type);
                    listener.onSuccessfulResponse2(response.getCode(), obj.getData());
                }
            };
        }
    }

    private Response.ErrorListener makeErrorListener(final RepositoryListener listener) {
        return new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                try {
                    String jError = new String(error.networkResponse.data);
                    ErrorEntity mError = gson.fromJson(jError, ErrorEntity.class);
                    // Invoke listener closure
                    listener.onErrorResponse(error.networkResponse.statusCode, mError.getDetails());
                } catch (Exception e) {
                    listener.onErrorResponse(404, e.getMessage());
                }
            }
        };
    }
}

This is very dependant by my needs, but I'll explain the general concept.

So I have a custom request, as explained in my question, and I want to parse it to the correct data type.

To be more specific, I could have a JSONArray data only on GET requests (paginated elements, etc...) so I need to find a way to distinguish between this two cases (of course, I know in which cases I'll get a List or an Object).

We cannot simply create POJO from Json within a generic class using its type (because Java Type Erasure), so we need object type upfront.
But what we can do is:

  • in our custom request, on parseNetworkResponse, do something like that:

    @Override
    protected Response<myResponse> parseNetworkResponse(NetworkResponse response) {
        try {
            // Using server charset
            myResponse mResponse = new myResponse();
    
            mResponse.setCode(response.statusCode);
            mResponse.setBody(new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers)));
    
            // Return new response
            return Response.success(mResponse, HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            // Normally use 'utf-8'
            return Response.error(new ParseError(e));
        }
    }
    

    In other words, copy the raw string response body onto a new object myResponse;

  • Response body will be eventually parsed in VolleyRestClient with the appropriate type passed as a GET/DELETE/POST argument;

  • makeSuccessfulListener and makeSuccessfulListener2 construct a Response.Listener from a RepositoryListener, which has 3 methods to override: onSuccessfulResponse for objects data, onSuccessfulResponse2 for list of objects data, onErrorResponse for 4XX/5XX errors;

    • Our data object/list will be parsed to more generics type (List and Object) and then passed to our custom listener RepositoryListener.

A full example for this approach:

public void getNewLogin(String nickname, String password,
                            final TextView author, final TextView title, final TextView text) {

        String json =
                (new StringBuilder()
                        .append("{ \"nickname\": \"")
                        .append(nickname)
                        .append("\", \"password\": \"")
                        .append(password)
                        .append("\" }")).toString();

        mRest.POST(false, "http://192.168.0.104:8000/api/session", json,
                new TypeToken<SuccessfulEntity<Login>>(){}.getType(),
                    new RepositoryListener() {
                        @Override
                        public void onSuccessfulResponse2(int code, List<Object> obj) {
                            // Nothing happens here
                        }

                        @Override
                        public void onSuccessfulResponse(int code, Object obj) {
                            UserSession mInstance = UserSession.getInstance(null);
                            Login newLogin = (Login) obj;

                            title.setText(newLogin.getToken());
                            mInstance.setToken(newLogin.getToken());

                            Log.i("onSuccessfulResponse", mInstance.getToken());
                            Log.i("onSuccessfulResponse", mInstance.getmAuthorizationToken());

                            if (newLogin.getUser() != null) {
                                author.setText(newLogin.getUser().getNickname());
                                text.setText(newLogin.getUser().getUniversity());
                            }
                        }

                        @Override
                        public void onErrorResponse(int code, String error) {
                            Log.i("onErrorResponse", error);
                        }
                    });

mRest is a VolleyRestClient object, which performs a POST request to that address with Type constructed by Gson TypeToken (remember, our body is a SuccessfulEntity).

Since we'll have an Object data for sure, we'll just override onSuccessfulResponse, cast data object to the same type T of SuccessfulEntity used in TypeToken, and do our dirty work.

I don't know if I was clear, this approach works, if some of you needs some clarification, just ask :)

Dan.see
  • 88
  • 11