Как улучшить Volley RequestQueue, чтобы удерживать loadingIndicator между последовательными вызовами и скрывать его в конце - PullRequest
1 голос
/ 04 июля 2019

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

У меня есть опыт работы на 1 год в Angular 4+, где легко объединить конечную точкузвонки, а затем выполнить некоторые действия после завершения звонков.Угловой пример:

this.showLoadingIndicator()
this.myEndpointService.getA
.pipe(
    tap(resultA => this.myEndpointService.getBFromA(resultA)),
    switchMap(resultB => this.myEndpointService.getCFromB(resultB)),
    tap(resultC => this.myC = resultC),
    finally(this.hideLoadingIndicator())
)
.subscribe (
    () => this.successDialog('so good so far'),
    error => {/*errors*/}
);

и (ограниченный) пример EndpointService:

getA(): Observable<any> {
    const url = `${this.apiEndpoint}/getA`;
    return this.http.get<any>(url);
  }

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

В Android у меня более длинный код, но с синглтоном и обобщениями логика течет хорошо.Я реализовал Volley как синглтон:

public class VolleyNetworkSingleton {
    private static VolleyNetworkSingleton instance;
    private static Context ctx;
    private RequestQueue requestQueue;

    private VolleyNetworkSingleton(Context context) {
        ctx = context;
        requestQueue = getRequestQueue();
    }
    /**
     * Ensures that the Class is instantiated only once
     * and the same instance is used throughout the application.
     *
     * @return the Class instance.
     */
    public static synchronized VolleyNetworkSingleton getInstance(Context context) {
        if (instance == null) {
            instance = new VolleyNetworkSingleton(context);
        }
        return instance;
    }

    /**
     * Ensures that the requestQueue is instantiated only once
     * and the same instance is used throughout the application.
     *
     * @return the RequestQueue.
     */
    public RequestQueue getRequestQueue() {
        if (requestQueue == null) {
            // getApplicationContext() is key, it keeps you from leaking the
            // Activity or BroadcastReceiver if someone passes one in.
            requestQueue = Volley.newRequestQueue(ctx.getApplicationContext());
        }
        return requestQueue;
    }

    /**
     * @param req the request to add
     * @param tag the tag to associate the request
     * @param <T> the generic Type used
     */
    public <T> void addToRequestQueue(Request<T> req, String tag) {
        req.setTag(tag);
        getRequestQueue().add(req);
    }

    /**
     * @param tag used to delete the associated Request
     */
    public void cancelAllRequests(String tag) {
        getRequestQueue().cancelAll(tag);
    }
}

Затем я настроил VolleyRequest с некоторыми функциями: - универсальное тело, которое автоматически анализируется как json, - универсальный ответ, который также анализируется обратно в объект - добавлены заголовки запроса

public class AuthorizedRequest extends Request {
    private static final String TAG = "AuthorizedRequest";
    private Response.Listener listener;
    /**
     * Declare a gson variable which will be used to convert the json response
     * to POJO
     **/
    private Gson gson;
    private Object body;
    /**
     * Declare a Class variable. This Class will represent the POJO. Basically
     * this variable will hold the instance of the java object which you
     * want as response
     **/
    private Class responseClass;
    private Context context;

    /**
     * Constructor for your custom class
     *
     * @param method        Http method
     * @param url           url of the request
     * @param requestBody   body of the request
     * @param responseClass Object type of the response
     * @param context       the context
     * @param listener      listener to notify success response
     * @param errorListener listener to notify error response
     */
    public <T> AuthorizedRequest(int method,
                                 String url,
                                 @Nullable T requestBody,
                                 Class responseClass,
                                 Context context,
                                 Response.Listener listener,
                                 Response.ErrorListener errorListener) {
        super(method, url, errorListener);
        gson = new GsonBuilder().registerTypeAdapter(Date.class, new DateDeserializer()).create();
        this.listener = listener;
        this.body = requestBody;
        this.context = context;
        this.responseClass = responseClass;
    }

    /**
     * This method needs to be implemented to parse the raw network response
     * and return an appropriate response type.This method will be called
     * from a worker thread. The response
     * will not be delivered if you return null.
     *
     * @param response Response payload as byte[],headers and status code
     **/
    @Override
    protected Response parseNetworkResponse(NetworkResponse response) {
        try {
            // First convert the NetworkResponse into a jsonstring.
            String jsonString = new String(
                    response.data,
                    HttpHeaderParser.parseCharset(response.headers, "utf-8"));
            // Then that json string can be converted into the required java.object<Gson>
            return Response.success(
                    gson.fromJson(jsonString, responseClass),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return Response.error(new ParseError(e));
        }
    }

    /**
     * This is called on the main thread with the object you returned in
     * parseNetworkResponse(). You should be invoking your callback interface
     * from here
     **/
    @Override
    protected void deliverResponse(Object response) {
        listener.onResponse(response);
    }

    /**
     * Parse the body of the request
     *
     * @return the parsed body
     */
    @Override
    public byte[] getBody() {
        String parsedBody = new String();
        try {
            parsedBody = parseBody(body);
            return parsedBody == null ? null : parsedBody.getBytes("utf-8");
        } catch (UnsupportedEncodingException uee) {
            VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", parsedBody, "utf-8");
            return null;
        }
    }

    /**
     * Applies the AccessToken logic and if successful, builds the headers
     *
     * @return the headers
     */
    @Override
    public Map<String, String> getHeaders() {
        try {

            if (MyUtils.isRefreshTokenExpired(context)) {
                return null;
            }
            if (MyUtils.isAccessTokenExpired(context)) {
                GoToLogin();
                return null;
            }

            Map<String, String> headers = new HashMap<>();
            headers.put("Content-Type", "application/json");
            return headers;
        } catch (IOException e) {
            Log.e(TAG, e.getMessage() + e.getStackTrace());
            String message = context.getResources().getString(R.string.no_internet_connection);
            return null;
        }
    }

    /**
     * Converts the object to string
     *
     * @param obj the object to parse
     * @param <T> the object's type
     * @return the string of the parsed body
     */
    private <T> String parseBody(T obj) {
        try {
            if (obj == null)
                return null;
            JSONObject jsonBody = new JSONObject(gson.toJson(obj));
            return jsonBody.toString();
        } catch (JSONException e) {
            e.printStackTrace();
            return null;
        }
    }

}

Тогда я просто использую это так:

AuthorizedRequest request = new AuthorizedRequest(
                Request.Method.GET,
                buildUrl(),
                null, /*no body needed*/
                MyObject.class, /*what i expect to receive*/
                getContext(),
                new Response.Listener<MyObject >() {
                    @Override
                    public void onResponse(MyObject response) {
                        if (response.getProp1() == null)
                            showEmptyPropMessage();
                        else {
                            myProp1.setText(response.getProp1());
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        error.printStackTrace();
                    }
                });
        // Add the request to the RequestQueue.
        VolleyNetworkSingleton.getInstance(getContext()).addToRequestQueue(request, "GET_MY_OBJECT");

Несмотря на необходимый объем кода, это прекрасно работает!

Но как мне объединить несколько запросов впоказать loadIndicator перед тем, как вызывать их, а затем скрыть?Должен ли я помещать каждый запрос внутри другого (на самом деле, внутри Response.Listener())?Потому что ... да, я могу это сделать (один внутри другого), и хостинг не так уж и плох ... но тогда мне придется скрывать loadingIndicator каждый раз, когда возникает ошибка.Более того, если вы хотите создать две цепочки вызовов , которые отличаются только порядком вызовов конечной точки, мне придется написать один и тот же код дважды.

пример getA затем getB case:

private void getAandThenB() {
    AuthorizedRequest reqA = new AuthorizedRequest(Method, url, null, A.class, context, Response.Listener { getB();}, Response.ErrorListener{});
    VolleyNetworkSingleton.getInstance(getContext()).addToRequestQueue(request, "GET_A");
}
private void getB() {
    AuthorizedRequest reqB = new AuthorizedRequest(Method, url, null, B.class, context, Response.Listener { /*do something*/}, Response.ErrorListener{});
    VolleyNetworkSingleton.getInstance(getContext()).addToRequestQueue(request, "GET_B");
}

Если в том же классе я также хочу обновить только A, мне нужно написать другую функцию, давайте назовем ее getJustA(), где единственным отличием является действие в ответе:

private void getJustA() {
    AuthorizedRequest reqA = new AuthorizedRequest(Method, url, null, A.class, context, Response.Listener { refreshA()}, Response.ErrorListener{});
    VolleyNetworkSingleton.getInstance(getContext()).addToRequestQueue(request, "GET_A");
}

Как видите, просто для выражения простого сценария дела это очень быстро выходит из-под контроля.

...