Я хотел бы показать свою загрузочную анимацию во время всех последовательных вызовов, а затем скрыть ее, какой бы ни был результат.
У меня есть опыт работы на 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");
}
Как видите, просто для выражения простого сценария дела это очень быстро выходит из-под контроля.