I have a ViewPager
and three webservice calls are made when ViewPager
is loaded simultaneously.
When first one returns 401, Authenticator
is called and I refresh the token inside Authenticator
, but remaining 2 requests are already sent to the server with old refresh token and fails with 498 which is captured in Interceptor and app is logged out.
This is not the ideal behaviour I would expect. I would like to keep the 2nd and 3rd request in the queue and when the token is refreshed, retry the queued request.
Currently, I have a variable to indicate if token refresh is ongoing in Authenticator
, in that case, I cancel all subsequent request in the Interceptor
and user has to manually refresh the page or I can logout the user and force user to login.
What is a good solution or architecture for the above problem using okhttp 3.x for Android?
EDIT: The problem I want to solve is in general and I would not like to sequence my calls. i.e. wait for one call to finish and refresh the token and then only send rest of the request on the activity and fragment level.
Code was requested. This is a standard code for Authenticator
:
public class CustomAuthenticator implements Authenticator {
@Inject AccountManager accountManager;
@Inject @AccountType String accountType;
@Inject @AuthTokenType String authTokenType;
@Inject
public ApiAuthenticator(@ForApplication Context context) {
}
@Override
public Request authenticate(Route route, Response response) throws IOException {
// Invaidate authToken
String accessToken = accountManager.peekAuthToken(account, authTokenType);
if (accessToken != null) {
accountManager.invalidateAuthToken(accountType, accessToken);
}
try {
// Get new refresh token. This invokes custom AccountAuthenticator which makes a call to get new refresh token.
accessToken = accountManager.blockingGetAuthToken(account, authTokenType, false);
if (accessToken != null) {
Request.Builder requestBuilder = response.request().newBuilder();
// Add headers with new refreshToken
return requestBuilder.build();
} catch (Throwable t) {
Timber.e(t, t.getLocalizedMessage());
}
}
return null;
}
}
Some questions similar to this: OkHttp and Retrofit, refresh token with concurrent requests
You can do this:
Add those as data members:
and then on the intercept method:
In this way you will only send 1 request to refresh the token and then for every other you will have the refreshed token.
You can try with this application level interceptor
You can set interceptor like this to okHttp instance
Hope this helps!!!!
It is important to note, that
accountManager.blockingGetAuthToken
(or the non-blocking version) could still be called somewhere else, other than the interceptor. Hence the correct place to prevent this issue from happening would be within the authenticator.We want to make sure that the first thread that needs an access token will retrieve it, and possible other threads should just register for a callback to be invoked when the first thread finished retrieving the token.
The good news is, that
AbstractAccountAuthenticator
already has a way of delivering asynchronous results, namelyAccountAuthenticatorResponse
, on which you can callonResult
oronError
.The following sample consists of 3 blocks.
The first one is about making sure that only one thread fetches the access token while other threads just register their
response
for a callback.The second part is just a dummy empty result bundle. Here, you would load your token, possibly refresh it, etc.
The third part is what you do once you have your result (or error). You have to make sure to call the response for every other thread that might have registered.
Just make sure to return
null
on all paths when using theresponse
.You could obviously use other means to synchronize those code blocks, like atomics as shown by @matrix in another response. I made use of
synchronized
, because I believe this to be the easiest to grasp implementation, since this is a great question and everyone should be doing this ;)The above sample is an adapted version of an emitter loop described here, where it goes into great detail about concurrency. This blog is a great source if you're interested in how RxJava works under the hood.
I found the solution with authenticator, the id is the number of the request, only for identification. Comments are in Spanish