Hurdles of Volley integration in Android Applications

Having been done with Google Volley (A networking library) integration in Spuul app recently, want to list all the hurdles I had faced during the process. So what’s nice about volley?

  1. One library for Image loading & Network calls.
  2. Performance, not claiming this one to be the best but works as intended for small Http requests.
  3. Fits nicely into Activity life cycle.

Since there are many blogs out there briefing integration part, I’ll run only through hurdles part. In every Http client library there are some basic things you need like access to request/response headers, injecting params into POST/GET calls etc. Unfortunately Volley wont make these things easy for you especially accessing response headers are not possible with StringRequest/JsonRequest objects. Reason was an intentional trade-off: less functionality for a simpler API But I’ve found some workarounds for each of these issues. Let’s go through them one by one (At the end of the post – I’ve written a consolidated Singleton Networking class using all listed points)

  • Accessing Response Headers:

In order to get hands on headers, we need to extend Request class and override parseNetworkResponse & deliverResponse  methods. Extract headers from NetworkResponse object in parseNetworkResponse and add all/needed headers to response in deliverResponse. Here’s how the CustomStringRequest looks like:

package com.*.android.networking;

import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.toolbox.HttpHeaderParser;

import java.io.UnsupportedEncodingException;
import java.util.Map;

/**
 * Created by Sri on 18/3/15.
 * A custom request to return {@link java.lang.String} response along with {@code Headers} wrapped in {@link StringResponseWithHeader}(a bean class to hold String response and headers Map) object.
 */
public class CustomStringRequest extends Request<StringResponseWithHeader> {

 private final Response.Listener<StringResponseWithHeader> mListener;
 private Map<String, String> mHeaders;

 /**
 * Creates a new request with the given method.
 *
 * @param method the request {@link Method} to use
 * @param url URL to fetch the string at
 * @param listener Listener to receive the String response with headers
 * @param errorListener Error listener, or null to ignore errors
 */
 public CustomStringRequest(int method, String url, Response.Listener<StringResponseWithHeader> listener,
 Response.ErrorListener errorListener) {
 super(method, url, errorListener);
 mListener = listener;
 }

 @Override
 protected void deliverResponse(StringResponseWithHeader response) {
 mListener.onResponse(response);
 }

 @Override
 protected Response<StringResponseWithHeader> parseNetworkResponse(NetworkResponse response) {
 String parsed;
 mHeaders = response.headers;
 try {
 parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
 } catch (UnsupportedEncodingException e) {
 parsed = new String(response.data);
 }
 StringResponseWithHeader obj = new StringResponseWithHeader();
 obj.setHeaders(mHeaders);
 obj.setResponseData(parsed);
 if (null != getTag() && getTag() instanceof String)
 obj.setRequestTag((String)getTag());
 return Response.success(obj, HttpHeaderParser.parseCacheHeaders(response));
 }
}

So this custom Request gives you a response object which has headers & response data embedded in it. For usage of this class, look in example class at the end of post.

  • Adding headers to Request Object

Adding headers to Request object involves in overriding getHeaders() method.

@Override
public Map<String, String> getHeaders() throws AuthFailureError {
    Map<String, String> headers = super.getHeaders();
    if (headers == null || headers.equals(Collections.emptyMap()))
        headers = new HashMap<String, String>();
    if (null != additionalHeaders && additionalHeaders.size() > 0)
        for (Map.Entry<String, String> entry : additionalHeaders.entrySet())
            headers.put(entry.getKey(), entry.getValue());
    headers.put("Accept", "application/****");
    return headers;
}
  • Adding POST/GET params to Request:

Adding params to POST/PUT calls is easy as you just need to override getParams() method. But it doesnt work for GET method since getParams() callback is only for PUT/POST.

Workaround is to append these params as query string to Url, here is how I did it in my code:

 Uri.Builder builder = Uri.parse(mRequestUrl).buildUpon();
 for (Map.Entry<String, String> entry : mRequestParams.entrySet())
 builder.appendQueryParameter(entry.getKey(), entry.getValue());
 url = builder.build().toString();
  • Request Timeouts, Retry Configurations

Volley makes it easy to configure timeouts & retry using setRetryPolicy method.

request.setRetryPolicy(new DefaultRetryPolicy(<strong>timeOut</strong>,
 DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
 DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

Consolidating all the above points, here is a Singleton class which provides RequestQueue instance:


package com.*.android.networking;

import android.content.Context;
import android.net.Uri;

import com.android.volley.AuthFailureError;
import com.android.volley.Cache;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Network;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.BasicNetwork;
import com.android.volley.toolbox.DiskBasedCache;
import com.android.volley.toolbox.HurlStack;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Sri on 16/3/15.
*/
public class SampleNetworking {
private static SampleNetworking mInstance;
private static Context mCtx;
private RequestQueue mRequestQueue;
final int DEFAULT_TIMEOUT = 30 * 1000;

public Request go(final String url, int methodType, Map<String, String> params, Response.Listener<StringResponseWithHeader> successResponseListener, Response.ErrorListener errorListener, final Map<String, String> additionalHeaders, Object tag, int timeOut) {
    if (methodType == Request.Method.GET || methodType == Request.Method.DELETE) {
        Uri.Builder builder = Uri.parse(url).buildUpon();
        for (Map.Entry<String, String> entry : params.entrySet())
        builder.appendQueryParameter(entry.getKey(), entry.getValue());
        url = builder.build().toString();
    }
    CustomStringRequest request = new CustomStringRequest(methodType, url, successResponseListener, errorListener) {
        @Override
        protected Map<String, String> getParams() throws AuthFailureError {
            return params;
        }

        @Override
        public Map<String, String> getHeaders() throws AuthFailureError {
            Map<String, String> headers = super.getHeaders();
            if (headers == null || headers.equals(Collections.emptyMap())) {
                headers = new HashMap<String, String>();
            }
            if (null != additionalHeaders && additionalHeaders.size() > 0)
            for (Map.Entry<String, String> entry : additionalHeaders.entrySet()) {
                headers.put(entry.getKey(), entry.getValue());
            }
            headers.put("Accept", "application/*");
            return headers;
        }
    };
    request.setRetryPolicy(new DefaultRetryPolicy(timeOut, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
    if (null != tag)
        request.setTag(tag);
    return mRequestQueue.add(request);
}

public static synchronized SampleNetworking getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new SampleNetworking(context);
    }
    return mInstance;
}
private SampleNetworking(Context ctx) {
    mCtx = ctx;
    getRequestQueue();
}

public RequestQueue getRequestQueue() {
    if (mRequestQueue == null) {
        // Instantiate the cache
        Cache cache = new DiskBasedCache(mCtx.getCacheDir(), 1024 * 1024); // 1MB cap
        // Set up the network to use HttpURLConnection as the HTTP client.
        Network network = new BasicNetwork(new HurlStack());
        // Instantiate the RequestQueue with the cache and network.
        mRequestQueue = new RequestQueue(cache, network);
        // Start the queue
        mRequestQueue.start();
    }
    return mRequestQueue;
}
}

Hope this helps for Volley newbies. let me know if you face any other issues.

Thanks
Advertisements