Guys, I need help, please help me figure it out. The application has a list of files that can be marked as read \ unread. With each click, a request is sent to the server to store the status of this document. But I discovered this bug:

04-06 06:30:00.384 22760-1489/me.android.app D/OkHttp: <-- 400 BAD REQUEST https://api.me/api/v1/2/setDocumentAttribute?token=q4B97JQBCKcZYCjVVw6v-cFMC4I2JoMd-MmCir7VvuLZl15J_Ztqm2JpbKwraRDmqWEyZOxJWzoy_eOKv0U2XX2a-9GL_7cXkOMyT20BLaqHb3nJSGoJnPU2cCFigEtC4TLhSIh_brpF_KNNeZ8_MqzEaomiTSYVtkx6RnL7ohPLPh8ZjMK2ow%3D%3D&documentID=33fe7dcb697f613c56f449754e923c53&path=Personal&attrName=isNew (99ms) 04-06 06:30:00.384 22760-1489/me.android.app D/OkHttp: Access-Control-Allow-Origin: * 04-06 06:30:00.384 22760-1489/me.android.app D/OkHttp: Content-Type: application/json 04-06 06:30:00.384 22760-1489/me.android.app D/OkHttp: Date: Thu, 06 Apr 2017 03:30:00 GMT 04-06 06:30:00.384 22760-1489/me.android.app D/OkHttp: Server: Apache/2.4.23 (Amazon) mod_wsgi/3.5 Python/2.7.12 04-06 06:30:00.384 22760-1489/me.android.app D/OkHttp: Content-Length: 97 04-06 06:30:00.384 22760-1489/me.android.app D/OkHttp: Connection: keep-alive 04-06 06:30:00.384 22760-1489/me.android.app D/OkHttp: {"message": "Input payload validation failed", "errors": {"token": "Security token"}} 

Api server has the following parameters:

  "parameters": [ { "in": "query", "type": "string", "description": "Pagination token", "name": "pageToken" }, { "in": "query", "type": "string", "description": "(True, False) Returns hierachy only, including empty folders", "name": "hierarchyOnly" }, { "in": "query", "type": "string", "description": "Parent folder ID", "name": "parent" }, { "in": "query", "type": "string", "description": "Valid security token", "name": "token" }, { "description": "An optional fields mask", "format": "mask", "type": "string", "name": "X-Fields", "in": "header" } ] 

In Android, I have the following ApiServiceInterface class with rest methods:

 @POST("setDocumentAttribute") Observable<String> setDocumentAttribute( @Query(TOKEN) String token, @Query(DOCUMENT_ID) String documentId, @Query("path") String path, @Query("attrName") String attrName); 

Below I cite a chain of method calls, I hope that it looks clear.

 DocCompanyPresenter public void onClickThumbnailToggle(DocItem docItem) { Log.d(TAG, "***onClickThumbnailToggle"); if (getView() != null) { String docId = docItem.getDocLink(); if (!TextUtils.isEmpty(docId)) { boolean toggle = docItem.getNewDocs()>0; if (toggle) { docItem.setNewDocs(0); // make star (read) Log.d(TAG, "***done read"); } else { docItem.setNewDocs(1); // make isNew (unread) Log.d(TAG, "***done unread"); } getView().updateListDocs(getDocPos(docItem.getDocLink())); getView().toggleDocument(leftPanelDocIdMark,!toggle); -> docId = docId.substring(0,docId.indexOf(".")); toggleDocument.toggleDocument(docId,docItem.getPath(),!toggle) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(); } } } FolderCompanyFragment @Override public void toggleDocument(String folderId, boolean toggle) { toggleDocument.actionToggleDocument(folderId,toggle); -> } -> MainActivity @Override public void actionToggleDocument(String folderId, boolean toggle) { Fragment fragment = getSupportFragmentManager().findFragmentByTag(FolderListFragment.TAG); if (fragment instanceof ActionToggleDocument) { ((ActionToggleDocument) fragment).actionToggleDocument(folderId,toggle); -> } } -> FolderListFragment @Override public void actionToggleDocument(String folderId, boolean toggle) { getPresenter().onFolderToggleDocument(folderId,toggle); } ->onClickThumbnailToggle -> toggleDocument.toggleDocument(docId,docItem.getPath(),!toggle) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(); <- RestFolderRepository @Override public Observable<String> toggleDocument(String documnetId, String path, boolean toggle) { return apiService.setDocumentAttribute(preferencesRepository.getToken(),documnetId,path,toggle?"isNew":"star") .subscribeOn(Schedulers.io()) .onErrorReturn(throwable -> { throwable.printStackTrace(); return null; }) .map(aVoid -> "OK"); } FolderCompanyFragment @Override public void onClickThumbnailToggle(DocItem docItem) { getPresenter().onClickThumbnailToggle(docItem); } 

All rest-methods are quite similar. I suspect that the "token" has problems with the lifetime. But how to solve this problem, I do not know yet. I would be grateful for any advice. If something is not clear in my question, then ask.

  • It is not completely clear what the problem is. If the token is short-lived, but there is an opportunity to update it, then why not make a strapping around the API layer? Retrofit most likely throws a HttpException, which can be handled with onErrorResumeNext in RxJava. - andrei_zaitcev
  • Yes, the problem seems to be that the token has a short lifetime, but could you explain in more detail what it means to make a binding around the API layer? - nicolas asinovich

1 answer 1

If the token manages to foul while the user uses the application, then it makes sense to add logic to update the token. The current implementation depends directly on the interface that Retrofit uses to create the REST adapter. Perhaps for architecture it would be better to do a wrapper around the API. It will look like this:

 class ApiWrapper implements ApiService { private final ApiService apiService; ApiWrapper(ApiService apiService) { this.apiService = apiService; } @Override @POST("setDocumentAttribute") public Observable<String> setDocumentAttribute( @Query(TOKEN) String token, @Query(DOCUMENT_ID) String documentId, @Query("path") String path, @Query("attrName") String attrName) { return apiService.setDocumentAttribute(token, documentId, path, attrName) .retryWhen(errors -> errors.flatMap(error -> { if (error instanceof HttpException) { HttpException exception = (HttpException) throwable; if (exception.code() == 400) { return apiService.refreshToken(); } } return Observable.error(throwable); })); } // etc } 

Then, in case of receiving a response with code 400, the API will try to update the token and retry the request.

  • Thank you very much, good advice! - nicolas asinovich