Google Volley

At Google I/O 2013, Google introduced a new networking library named Volley, which is a library that makes networking for Android apps easier and most importantly, faster. Let’s face it; most Android apps require a fairly robust network stack. If you are lucky, your app doesn’t need to queue up requests and cache data, but if you are like the rest of us, Volley is your answer. So, what makes Volley awesome?

– It serializes network requests via priority and sequence.

– It optionally caches these requests so as to not made extra network requests.

– It gracefully handles duplicate requests.

– It wraps all of those pesky Http errors in readable object-oriented errors that make debugging API calls significantly easier.

– It comes with an asynchronous ImageLoader and NetworkImageView that work well in ListViews.

So, now you are thinking to yourself, “Volley sounds great! I’m definitely going to use it!” That’s great to hear, but first ask yourself these two questions.

– Does my app need to stream data?

– Does my app need to download large files?

If you answered yes to either, then you might not need Volley. Volley is best suited for REST services that involve fairly small payloads (in the KB or <2MB range) or with downloading images in a ListView.

Okay, you’ve decided that Volley is for you. Let’s go over some of the main features.

Make a Singleton for your RequestQueue

Volley manages the network requests through the RequestQueue class. The best was to use this class is to make a singleton object and initialize the queue in your Application class. In order to keep our code separate, let’s make a VolleyManager class. Don’t worry about the cacheSize right now. We will get to that later.

import android.content.Context;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

public class VolleyManager {

    /** Internal instance variable. */
    private static VolleyManager sInstance;

    /** The request queue. */
    private RequestQueue mRequestQueue;

    /**
     * Volley image loader
     */
    private ImageLoader mImageLoader;

    /**
     * Image cache implementation
     */
    private ImageLoader.ImageCache mImageCache;

    private VolleyManager() {
        // no instances
    }

    /**
     * This is the initializer.
     * @param context Your application context.
     * @param cacheSize The size of your image cache.
     */
    public static void init(Context context, int cacheSize) {
        if (sInstance == null) {
            sInstance = new VolleyManager();
            sInstance.mRequestQueue = Volley.newRequestQueue(context);
            sInstance.mImageCache = new BitmapLruImageCache(cacheSize);
            sInstance.mImageLoader = new ImageLoader(VolleyManager.getRequestQueue(), sInstance.mImageCache);
        }
    }

    /**
     * Gets the image loader from the singleton.
     * @return The RequestQueue.
     * @throws java.lang.IllegalStateException This is thrown if init has not been called.
     */
    public static RequestQueue getRequestQueue() {
        if (sInstance == null) {
            throw new IllegalStateException("The VolleyManager must be initialized.");
        }
        return sInstance.mRequestQueue;
    }

    /**
     * Gets the image loader from the singleton.
     * @return The ImageLoader.
     * @throws java.lang.IllegalStateException This is thrown if init has not been called.
     */
    public static ImageLoader getImageLoader() {
        if (sInstance == null) {
            throw new IllegalStateException("The VolleyManager must be initialized.");
        }
        return sInstance.mImageLoader;
    }
}

}

This class can then be initialized by calling VolleyManager.init(getApplicationContext()) from your Launcher Activity’s onCreate(…) or by calling VolleyManager.init(this) from your subclass of Application.

Forming a Request

Once the RequestQueue has been initialized, you can form your request. Volley provides several Requests in the toolbox, including a basic StringRequest, an ImageRequest, a JsonObjectRequest, and a JsonArrayRequest. You can also write your own requests by subclassing the Request class.

Here is how you would make a JsonObjectRequest.

JSONObject postBody = new JSONObject();
postBody.put("fname", "Kevin");
postBody.put("lname", "Marlow");

String url = "http://www.myurl.com ";

JsonObjectRequest myRequest = new JsonObjectRequest(Request.Method.POST, url, postBody, new Response.Listener() {
    @Override
    public void onResponse(JSONObject response) {
        Log.i("LOGTAG", "Response is " + response.toString());
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        Log.i("LOGTAG", "Error with status code " + error.networkResponse.statusCode);
    }
});

Now that we have a request made, we can add it to the queue by calling VolleyManager.getRequestQueue().add(myRequest);.

You can add as many request as you need. They will be put in the queue and accessed based on order added (FIFO).

Set Priority and Headers

One of the best features of Volley is Request prioritization. By calling add, we were simply adding requests to the queue with the default priority of Priority.NORMAL. Let’s say we have a request that is more or less important than our other requests. We can easily change this by subclassing Request or one of its subclasses and overriding the getPriority() method. Let’s subclass that JsonObjectRequest example to see how it is done.

public class HighPriorityRequest extends JsonObjectRequest {

    public HighPriorityRequest(int method, String url, JSONObject jsonRequest, Response.Listener listener, Response.ErrorListener errorListener) {
        super(method, url, jsonRequest, listener, errorListener);
    }

    public HighPriorityRequest(String url, JSONObject jsonRequest, Response.Listener listener, Response.ErrorListener errorListener) {
        super(url, jsonRequest, listener, errorListener);
    }

    @Override
    public Map getHeaders() throws AuthFailureError {
        Map headers = new HashMap();
        headers.put("Custom-Header", "Header data!");
        return headers;
    }

    @Override
    public Priority getPriority() {
        return Priority.IMMEDIATE;
    }
}

In the example, we return Priority.IMMEDIATE as the priority. When Volley sorts the queue, this request will start before all priorities lower than it. In the example, you can also see that we’ve added a custom header by overriding the getHeaders() method. This is useful for when you need to add header data to your http requests.

Utilizing the ImageCache and NetworkImage View

Another great feature of volley is automatic network image downloading. This feature is highly useful when constructing ListViews with image data. Remeber when I said we would get to the cacheSize later? Well, this is where it comes into play. In order to use the ImageLoader, we must create our own Memory Cache implementation. When it comes to in-memory caching there is little support from the Volley library. However, coding your own can be fairly straightforward if you follow a few steps. We want to subclass the LruCache that is provided by the android.util library and then we want to implement the ImageCache interface provided by the ImageLoader. Below you can see a basic implementation of a BitmapLruCache.

// This call initializes our BitmapLruImageCache
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
int heapSize = am.getMemoryClass();
VolleyManager.init(this, (heapSize * 1024 * 1024 / 8));
...
...
public class BitmapLruImageCache extends LruCache implements ImageLoader.ImageCache {

    public BitmapLruImageCache(int maxSize) {
        super(maxSize);
    }

    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getByteCount();
    }

    @Override
    public Bitmap getBitmap(String url) {
        return get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        put(url, bitmap);
    }
}

The size of the cache will be based on the maximum amount of memory available at runtime. This can be found using the android ActivityManager.getMemoryClass() method. This method returns the total allocated heap size for our app in megabytes. We used /8 to get a fraction of that memory and then multiple twice by 1024 to get the value in bytes.

Now that we have initialized the ImageLoader, all we have to do is replace the ImageView layouts in our ListView row layout with NetworkImageView provided by Volley.

NetworkImageView
    android:id="@+id/networkImage"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:padding="5dp"
    />

Then you can set up this view in your adapter’s getView(…).

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    final ViewHolder holder;

    if (convertView == null) {
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.list_item_layout, null);
        holder = new ViewHolder();
        holder.imageView = (NetworkImageView) convertView.findViewById(R.id.networkImage);
        holder.firstLine = (TextView) convertView.findViewById(R.id.firstLine);
        holder.secondLine = (TextView) convertView.findViewById(R.id.secondLine);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    final DummyItem item = getItem(position);
    holder.firstLine.setText(item.getName());
    holder.secondLine.setText(item.getArtist());
    holder.imageView.setImageUrl(item.getImageUrl(), VolleyManager.getImageLoader());
    
    return convertView;
}

You will now have images being downloaded asynchronously, stored in the cache, and set as the image bitmap in the NetworkImageView automatically. That’s how easy it is!

Final Thoughts

Thank you for reading this Volley overview. I hope you are able to use Volley to make beautiful, responsive apps. There are still some more advanced topics to discuss, but let’s leave those for a later post.