Volley REST клиент, използващ 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/списъкът на данните вече е анализиран от 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), така че имаме нужда от тип обект предварително.
Но това, което можем да направим е:

  • в нашата персонализирана заявка, на 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 заявка до този адрес с Type, конструиран от Gson TypeToken (не забравяйте, че нашето тяло е SuccessfulEntity).

Тъй като със сигурност ще имаме данни за обект, просто ще заменим onSuccessfulResponse, ще прехвърлим обект с данни към същия тип T на SuccessfulEntity, използван в TypeToken, и ще свършим мръсната си работа.

Не знам дали бях ясен, този подход работи, ако някой от вас има нужда от разяснение, просто попитайте :)

person Dan.see    schedule 15.12.2015