Клиент REST Volley с использованием JSON

Я хочу взаимодействовать с веб-службой RESTful, которая отвечает только в формате JSON. Любой успешный ответ сервера имеет следующий синтаксис:

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

при ответе на ошибку:

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

Итак, я сделал два класса в своем проекте Android следующим образом (для отражения GSON):

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;
    }
}

Для успешного ответа я сделал общий, потому что я не хочу анализировать данные JSON в переопределенном 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;
    }
}

Теперь, поскольку моему серверу RESTful требуются некоторые настраиваемые заголовки, мне нужно создать подкласс Request, но я не знаю, от какого класса мне нужно наследоваться.
Я видел этот вопрос: Отправить POST-запрос с данными JSON с помощью Volley и хотя бы сделать что-то подобное.

По сути, я хочу создать новый класс (VolleyRestClient), который имеет методы GET, POST, DELETE и маршрутизацию API, и с помощью этого класса выполнять все необходимые мне запросы.

Методы этого класса должны сделайте новый пользовательский запрос и проанализируйте ответ новых объектов, таких как SuccessfulEntity и ErrorEntity, а затем проанализируйте данные в сервисе/потоке, которые вызывают вызов VolleyRestClient.

Как я могу это сделать?


person Dan.see    schedule 14.12.2015    source источник
comment
почему бы не Entity<T>, который будет иметь все реквизиты из SuccessfulEntity<T> и ErrorEntity... и сделать реквизиты, которые не распространены в этих классах, необязательными... и для проверки ошибок вы должны использовать entity.getError() != null   -  person Selvin    schedule 14.12.2015
comment
@Selvin, а как мне заставить Class‹Entity‹T›› перейти к пользовательскому запросу для синтаксического анализа GSON?   -  person Dan.see    schedule 14.12.2015
comment
так же, как вы хотите сделать с SuccessfulEntity‹T› ... поэтому, очевидно, вы должны знать тип T перед созданием пользовательского запроса   -  person Selvin    schedule 14.12.2015
comment
Решено создание 2 отдельных классов, SuccessfulObjEntity и SuccessfulListEntity, унаследованных от класса Entity, но тогда данные obj/list уже анализируются Gson, я прав? Мне просто нужно сделать приведение к соответствующему типу?   -  person Dan.see    schedule 14.12.2015


Ответы (1)


После долгой борьбы с дженериками и стиранием типов я, наконец, сделал это.
Итак, я публикую это для тех, у кого такая же проблема, как у меня, и кому нужно решение, не волнуясь.

Мои ErrorEntity и SuccessfulEntity остались прежними, но я создал новый интерфейс RepositoryListener, например:

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);
}

Затем я создал класс VolleyRestClient, например:

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());
                }
            }
        };
    }
}

Это очень зависит от моих потребностей, но я объясню общую концепцию.

Итак, у меня есть пользовательский запрос, как объясняется в моем вопросе, и я хочу проанализировать его на правильный тип данных.

Чтобы быть более конкретным, я мог бы иметь данные JSONArray только для запросов GET (элементы с разбивкой на страницы и т. д.), поэтому мне нужно найти способ различать эти два случая (конечно, я знаю, в каких случаях я получу список или объект).

Мы не можем просто создать POJO из Json внутри универсального класса, используя его тип (потому что Java Type Erasure), поэтому нам нужен тип объекта upfront.
Но что мы можем сделать, так это:

  • в нашем пользовательском запросе на parseNetworkResponse сделайте что-то вроде этого:

    @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));
        }
    }
    

    Другими словами, скопируйте необработанное текстовое тело ответа в новый объект myResponse;

  • Тело ответа будет в конечном итоге проанализировано в VolleyRestClient с соответствующим типом, переданным в качестве аргумента GET/DELETE/POST;

  • makeSuccessfulListener и makeSuccessfulListener2 создают Response.Listener из RepositoryListener, который имеет 3 переопределяемых метода: onSuccessfulResponse для данных объектов, onSuccessfulResponse2< /em> для списка данных объектов, onErrorResponse для ошибок 4XX/5XX;

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

Полный пример для этого подхода:

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 — это объект VolleyRestClient, который выполняет POST-запрос к этому адресу с типом, созданным Gson TypeToken (помните, что наше тело — это SuccessfulEntity).

Поскольку у нас наверняка будут данные Object, мы просто переопределим onSuccessfulResponse, приведем объект данных к тому же типу T SuccessfulEntity, который используется в TypeToken, и выполним нашу грязную работу.

Я не знаю, ясно ли я выразился, этот подход работает, если кому-то из вас нужны разъяснения, просто спросите :)

person Dan.see    schedule 15.12.2015