I have a simple authentication system implemented using Volley. It goes like this: Get a token from server on login -> an hour later, this token expires -> when it expires, we will find that out on a failed API call, so we should (on retry) -> fetch a new token when that call fails and then -> retry the original call.
I've implemented this, and the token is returning successfully, but because I think I'm doing something wrong with the Volley RequestQueue, the original request uses all it's retrys before the new and valid token is able to be used. Please see the following code:
public class GeneralAPICall extends Request<JSONObject> {
public static String LOG_TAG = GeneralAPICall.class.getSimpleName();
SessionManager sessionManager; //instance of sessionManager needed to get user's credentials
private Response.Listener<JSONObject> listener; //the response listener used to deliver the response
private Map<String, String> headers = new HashMap<>(); //the headers used to authenticate
private Map<String, String> params; //the params to pass with API call, can be null
public GeneralAPICall(int method, String url, Map<String, String> params, Context context, Response.Listener<JSONObject> responseListener, Response.ErrorListener errorListener) {
super(method, url, errorListener);
sessionManager = new SessionManager(context); //instantiate
HashMap<String, String> credentials = sessionManager.getUserDetails(); //get the user's credentials for authentication
this.listener = responseListener;
this.params = params;
//encode the user's username and token
String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME)
+ Constants.APIConstants.Characters.CHAR_COLON
+ credentials.get(Constants.SessionManagerConstants.KEY_TOKEN)).getBytes(), Base64.NO_WRAP));
Log.v(LOG_TAG, loginEncoded); //TODO: remove
this.headers.put(Constants.APIConstants.BasicAuth.AUTHORIZATION, Constants.APIConstants.BasicAuth.BASIC + loginEncoded); //set the encoded information as the header
setRetryPolicy(new TokenRetryPolicy(context)); //**THE RETRY POLICY**
}
The retry policy I set is defined as default, but I implement my own retry method as such:
@Override
public void retry(VolleyError error) throws VolleyError {
Log.v(LOG_TAG, "Initiating a retry");
mCurrentRetryCount++; //increment our retry count
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
if (error instanceof AuthFailureError) { //we got a 401, and need a new token
Log.v(LOG_TAG, "AuthFailureError found!");
VolleyUser.refreshTokenTask(context, this); //**GET A NEW TOKEN**
}
if (!hasAttemptRemaining()) {
Log.v(LOG_TAG, "No attempt remaining, ERROR");
throw error;
}
}
The refresh token task defines a RefreshAPICall
public static void refreshTokenTask(Context context, IRefreshTokenReturn listener) {
Log.v(LOG_TAG, "refresh token task called");
final IRefreshTokenReturn callBack = listener;
RefreshAPICall request = new RefreshAPICall(Request.Method.GET, Constants.APIConstants.URL.GET_TOKEN_URL, context, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
try {
String token = response.getString(Constants.APIConstants.Returns.RETURN_TOKEN);
Log.v(LOG_TAG, "Token from return is: " + token);
callBack.onTokenRefreshComplete(token);
} catch (JSONException e) {
callBack.onTokenRefreshComplete(null); //TODO: log this
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.v(LOG_TAG, "Error with RETRY : " + error.toString());
}
});
VolleySingleton.getInstance(context).addToRequestQueue(request);
}
Our RefreshAPICall definition:
public RefreshAPICall(int method, String url, Context context, Response.Listener<JSONObject> responseListener, Response.ErrorListener errorListener) {
super(method, url, errorListener);
sessionManager = new SessionManager(context); //instantiate
HashMap<String, String> credentials = sessionManager.getRefreshUserDetails(); //get the user's credentials for authentication
this.listener = responseListener;
//encode the user's username and token
String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME)
+ Constants.APIConstants.Characters.CHAR_COLON
+ credentials.get(Constants.SessionManagerConstants.KEY_PASSWORD)).getBytes(), Base64.NO_WRAP));
this.headers.put(Constants.APIConstants.BasicAuth.AUTHORIZATION, Constants.APIConstants.BasicAuth.BASIC + loginEncoded); //set the encoded information as the header
setTag(Constants.VolleyConstants.RETRY_TAG); //mark the retry calls with a tag so we can delete any others once we get a new token
setPriority(Priority.IMMEDIATE); //set priority as immediate because this needs to be done before anything else
//debug lines
Log.v(LOG_TAG, "RefreshAPICall made with " + credentials.get(Constants.SessionManagerConstants.KEY_USERNAME) + " " +
credentials.get(Constants.SessionManagerConstants.KEY_PASSWORD));
Log.v(LOG_TAG, "Priority set on refresh call is " + getPriority());
Log.v(LOG_TAG, "Tag for Call is " + getTag());
}
I set the priority of this request as high so that it gets triggered before the one that failed, so once we get a token the original call can then fire with the valid token.
Finally, on response I delete any other tasks with the retry tag (in case multiple API calls failed and made multiple retry calls, we don't want to overwrite the new token multiple times)
@Override
public void onTokenRefreshComplete(String token) {
VolleySingleton.getInstance(context).getRequestQueue().cancelAll(Constants.VolleyConstants.RETRY_TAG);
Log.v(LOG_TAG, "Cancelled all retry calls");
SessionManager sessionManager = new SessionManager(context);
sessionManager.setStoredToken(token);
Log.v(LOG_TAG, "Logged new token");
}
Unfortunately, the LogCat is showing me that all the retries are happening before we use the token. The token is coming back successfully, but it's obvious that the IMMEDIATE priority is having no effect on the order that the queue dispatches the calls.
Any help on how to ensure my RefreshAPICall is fired before the other tasks would be greatly appreciated. I'm wondering if Volley considers the RefreshAPICall as a subtask of the original failed task, and so it attempts to call that original task for its number of retrys until those are out, and then fires off the RefreshAPICall.
LogCat (not sure how to make this look pretty):
05-05 16:12:07.145: E/Volley(1972): [137] BasicNetwork.performRequest:
Unexpected response code **401 for https://url.me/api/get_friends**
05-05 16:12:07.145: V/TokenRetryPolicy(1972): Initiating a retry
05-05 16:12:07.145: V/TokenRetryPolicy(1972): AuthFailureError found!
05-05 16:12:07.146: V/VolleyUser(1972): refresh token task called
05-05 16:12:07.146: V/RefreshAPICall(1972): RefreshAPICall made with username user_password
05-05 16:12:07.147: V/RefreshAPICall(1972): Priority set on refresh call is HIGH
05-05 16:12:07.147: V/RefreshAPICall(1972): Tag for Call is retry
05-05 16:12:07.265: E/Volley(1972): [137] BasicNetwork.performRequest: Unexpected response code **401 for https://url.me/api/get_friends**
05-05 16:12:07.265: V/TokenRetryPolicy(1972): Initiating a retry
05-05 16:12:07.265: V/TokenRetryPolicy(1972): AuthFailureError found!
05-05 16:12:07.265: V/VolleyUser(1972): refresh token task called
05-05 16:12:07.265: V/RefreshAPICall(1972): RefreshAPICall made with user user_password
05-05 16:12:07.265: V/RefreshAPICall(1972): Priority set on refresh call is HIGH
05-05 16:12:07.265: V/RefreshAPICall(1972): Tag for Call is retry
05-05 16:12:07.265: V/TokenRetryPolicy(1972): No attempt remaining, ERROR
05-05 16:12:08.219: I/Choreographer(1972): Skipped 324 frames! The application may be doing too much work on its main thread.
05-05 16:12:08.230: V/RefreshAPICall(1972): Response from server on refresh is: {"status":"success","token":"d5792e18c0e1acb3ad507dbae854eb2cdc5962a2c1b610a6b77e3bc3033c7f64"}
05-05 16:12:08.230: V/VolleyUser(1972): Token from return is: d5792e18c0e1acb3ad507dbae854eb2cdc5962a2c1b610a6b77e3bc3033c7f64
05-05 16:12:08.231: V/TokenRetryPolicy(1972): Cancelled all retry calls
05-05 16:12:08.257: V/SessionManager(1972): New Token In SharedPref is: d5792e18c0e1acb3ad507dbae854eb2cdc5962a2c1b610a6b77e3bc3033c7f64
05-05 16:12:08.257: V/TokenRetryPolicy(1972): Logged new token