When developing an app on native Android you definitely at some point happen to need to make a network call, say, calling a JSON api. Being either your own well known or third party api you always need to get your hands on that tedious boilerplate code: create an AsyncTask and in it’s doInBackground() method implement the HTTP call using either the buggy and already deprecated apache HttpClient library, or take the HttpURLConnection approach. And what about concurrent network calls? synchronisation? scheduling? IO exceptions? request retry and back-off? and what about caching?
This is were Volley comes handy. Android engineers @ Google took all their lessons learnt while developing quality production apps over these years and wrapped them up in this library which basically provides an interface to step up and think less on the underlying network layer, caching, synchronisation and boilerplate network transports and focus more on logical operations and how to intersect network with the UI hierarchy.
It offers out of the box:
- Trivial Image downloading and JSON apis access
- Memory and disk caching
- Parallel request execution and priorization
- Powerful customisation abilities
- Debugging and tracing tools
- among others..
Suitable for RPC like operations, fetching metadata and updating the UI, not so much for heavy data download or media streaming. Regardless, you are not restricted to take a volley-only approach as you can still use it for your RPCs and take a different approach in another scenario, like using a DownloadManager for heavy file download.
I recently faced a project in which I had the need for fast networking and cacheable lightweight responses. So after surfing the web looking at the docs and various examples, I figured Volley suited this scenario perfectly and decided to give it a try. I found it extremely intuitive to use, time-saving and above all less error prone as it abstracted away all the networking code I would have needed to make the network calls and handle memory and disk caching.
I will present below some of the features I’ve been testing and using and show some I find particularly interesting for you to give it a try. I will also show how things are done using an HttpUrlConnection approach to show how Volley simplifies, reduces size and errors in our code and saves us time.
I will be building a simple Chuck Norris jokes app to show you how things work on Volley for those well known patterns compared to other networking approaches.
I will be using the rest api provided by the Internet Chuck Norris Database http://www.icndb.com/api/
Main Activity
Our Main Activity basically instantiates the Volley RequestQueue and inflates a main view that consists of two Buttons and a TextView. First Button fetches a random joke using HttpUrlConnection approach, and second Button does the same but using Volley. Finally, the TextView is the one populated with the joke fetched by the service. Instantiating the request queue on the Activity is not a good practice, as it will live in the process that holds the Activity as long as the activity is running. Thus, it should be instantiated at application scope to be available and used from different app components in either a custom Application class or using a singleton. As for this single Activity example, we will leave the queue at Activity scope.
... <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/activity_vertical_margin"> <Button android:id="@+id/first_approach_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Fetch Joke" android:layout_marginRight="@dimen/activity_horizontal_margin" android:onClick="fetchData"/> <Button android:id="@+id/volley_approach_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@id/first_approach_btn" android:text="Volley It!" android:onClick="fetchData"/> </RelativeLayout> <TextView android:id="@+id/joke" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Press a button to get a Random joke"/> ...
JSON api request
Below is the code for a JSON api call using HttpURLConnection approach. Basically, we will need to create an AsyncTask, override the doInBackground method with our network call and then post the results to the UI thread in the onPostExecute method.
public class RandomJokeAsyncTask extends AsyncTask<Void, Void, String>{ @Override protected String doInBackground(Void... voids) { URL randomJoke; HttpURLConnection connection = null; try { randomJoke = new java.net.URL(RANDOM_URL); connection = (HttpURLConnection) randomJoke.openConnection(); connection.setReadTimeout(10000); connection.setConnectTimeout(15000); connection.setRequestMethod("GET"); connection.setDoInput(true); connection.connect(); int response = connection.getResponseCode(); Log.d(TAG, "The response code is: " + response); Scanner scanner = new Scanner(connection.getInputStream(), "UTF-8"); StringBuilder sb = new StringBuilder(); while (scanner.hasNextLine()){ sb.append(scanner.nextLine()); } String content = sb.toString(); Log.d(TAG, "Service Response => " + content); JSONObject jsonResponse = new JSONObject(content); // Response example: { "type": "success", "value": { "id": , "joke": } } return jsonResponse.getJSONObject("value").getString("joke"); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "Something went wrong while" + " retrieving json " + e.toString()); } catch (JSONException e) { e.printStackTrace(); Log.e(TAG, "Something went wrong while" + " parsing json from " + e.toString()); } finally { if(connection != null) connection.disconnect(); } return null; } @Override public void onPostExecute(String joke){ mJokeTextView.setText(joke); } }
Let’s see now how the same network call is approached with Volley
JsonObjectRequest request = new JsonObjectRequest( Request.Method.GET, RANDOM_URL, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { try { Log.d(TAG, "Service Response => " + response.toString()); mJokeTextView.setText(response.getJSONObject("value").getString("joke")); } catch (JSONException e) { e.printStackTrace(); Log.d(TAG, "Error parsing response JSON object"); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { if (error.networkResponse != null) { Log.d(TAG, "Error Response code: " + error.networkResponse.statusCode); } } } ); mRequestQueue.add(request);
You can see that most of the boilerplate code is gone with Volley (try-catches, HttpUrlConnection setup, response parsing, etc); also note that onResponse callback is practically the same as the onPostExecute callback on the AsyncTask, which means that portability to Volley does not entail major changes in your app. Volley also allows you to pass in an error listener in case the requests fail with, say, an HTTP 404 or 500 response code.
Apart from JSON object requests, Volley also provides support for raw text request (StringRequest) which you can then parse to whatever you need to; JSON array requests that it parses a JSON array object as the response; and powerful Bitmap request handling which we will discuss later.
So far so good.. but, what if we want to cache the joke to prevent the roundtrip to the server.
Caching
Let’s see how we can handle caching using the HttpUrlConnection approach. We can make use of the android.net.http.HttpResponseCache class introduced in Android 4.0+ devices. Note that the following code will not work on Android versions previous to 4.0, but for the sake of this post I will only use cache if device’s SDK version is greater or equal to 4.0 (Ice Cream Sandwich).
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { ... }
I will first need to install HttpResponseCache in order to retrieve our cached response later on. In this example cache will be stored in the application’s secure cache directory getCacheDir().
private void enableHttpResponseCache() { try { long httpCacheSize = 10 * 1024 * 1024; // 10 MiB File httpCacheDir = new File(getCacheDir(), "http"); HttpResponseCache.install(httpCacheDir, httpCacheSize); } catch (Exception ex) { Log.d(TAG, "HTTP response cache is unavailable."); } }
Following is the code to retrieve an entry from the application’s cache I installed above.
private String fetchFromHTTPUrlConnectionCache(HttpURLConnection connection, URI uri) { try { HttpResponseCache responseCache = HttpResponseCache.getInstalled(); if(responseCache != null){ CacheResponse cacheResponse = responseCache.get(uri, "GET", connection.getRequestProperties()); Scanner scanner = new Scanner(cacheResponse.getBody(), "UTF-8"); StringBuilder sb = new StringBuilder(); while (scanner.hasNextLine()){ sb.append(scanner.nextLine()); } return sb.toString(); } } catch (Exception ex) { ex.printStackTrace(); } return null; }
So basically, I will go ahead and wire this with our HttpUrlConnection by adding the following code before initiating the connection to check if we have a cached entry available and return that one instead.
// instantiate connection ... // check for cache entry if we are on Android 4.0 + device if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if(HttpResponseCache.getInstalled() == null) enableHttpResponseCache(); String cachedResponse = fetchFromHTTPUrlConnectionCache(connection, new URI(RANDOM_URL)); if (cachedResponse != null) { fetchedFromCache = true; JSONObject response = new JSONObject(cachedResponse); return response.getJSONObject("value").getString("joke"); } } // no cache entry available connection.connect() ...
Let’s now see how this would be approached with Volley.
By default, Volley parses the Cache-Control HTTP headers set in the response to determine if it should be cached or not, and if so, determines the cache entry TTL.
In this case, the api returns Cache-Control: no-cache, must-revalidate which means that response will not be cached at all. As I mentioned above, Volley has powerful customisation abilities and, as such, we can force it to cache the response by basically overriding the method that parses the response headers. I will override the JsonObjectRequest.parseNetworkResponse method and build the JSONObject response myself from the response raw byte[] array data, and set the Cache entry for the current response. I will force the entry to be cached 24 hours in this case, you can set this arbitarily to whichever value you need to.
@Override protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) { try { String jsonString = new String(response.data); JSONObject jsonObject = new JSONObject(jsonString); // force response to be cached Map<String, String> headers = response.headers; long cacheExpiration = 24 * 60 * 60 * 1000; // in 24 hours this cache entry expires completely long now = System.currentTimeMillis(); Cache.Entry entry = new Cache.Entry(); entry.data = response.data; entry.etag = headers.get("ETag"); entry.ttl = now + cacheExpiration; entry.serverDate = HttpHeaderParser.parseDateAsEpoch(headers.get("Date")); entry.responseHeaders = headers; return Response.success(jsonObject, entry); } catch (JSONException e) { e.printStackTrace(); return Response.error(new VolleyError("Error parsing network response")); } }
The following method checks for a valid cache entry and will be called before building and queuing the request. If entry is available, it builds a JSONObject from the cached entry byte[] array data and displays it in our TextView.
private boolean fetchFromVolleyCache(){ try{ Cache cache = mRequestQueue.getCache(); Cache.Entry entry = cache.get(RANDOM_URL); if(entry != null) { JSONObject cachedResponse = new JSONObject(new String(entry.data, "UTF-8")); mJokeTextView.setText(cachedResponse.getJSONObject("value").getString("joke")); Toast.makeText(this, "Joke fetched from cache!", Toast.LENGTH_SHORT).show(); return true; } }catch (UnsupportedEncodingException | JSONException e){ e.printStackTrace(); Log.d(TAG, "Error fetching joke from cache for entry: " + RANDOM_URL); } return false; }
Request cancellation
Another great feature is request cancelling – note this is also available when working with AsyncTasks. For instance, you can keep track of your in-flight AsyncTasks and cancel them all, say, on the onStop callback of the activity.
for (AsyncTask asyncTask : mTrackedAsyncTasks){ asyncTask.cancel(true); }
You can also keep track of your in-flight Volley requests and cancel them all.
for(Request<?> request : mTrackedRequestQueue){ request.cancel(); }
Not a major difference here. However, Volley also provides the ability to create batches or scopes of request to cancel by tagging the requests with a certain object. In this case I cancel all the requests in the Activity scope.
request.setTag(this); mRequestQueue.add(request);
@Override protected void onStop(){ super.onStop(); mRequestQueue.cancelAll(this); }
This comes handy when, for instance, you have a tabbed view pager with multiple fragments and multiple requests per fragment. So you can go ahead and tag your requests with the Tab object and cancel the requests when the Tab is un-selected and it’s content is not visible to the user and cannot interact with it.
Volley also allows you to provide a filter to determine which requests to cancel. For example, you would want to cancel all your image requests at a given moment, and keep your in-flight JSON requests in the request queue.
mRequestQueue.cancelAll(new RequestQueue.RequestFilter() { @Override public boolean apply(Request<?> request) { return request instanceof ImageRequest; } });
Priorization
Volley also provides out of the box request priorization. Requests are processed from higher priorities to lower priorities, in FIFO order. For example, you can set your main metadata requests with a higher priority that your image request so you don’t need to wait until your image assets load to allow the user to see your data and interact with it. You can set this either inline at request creation time, or extending one of the base Request classes provided in the toolbox and Override the getPriority() method – see Image loading example below.
@Override public Priority getPriority() { return Priority.HIGH; }
Request Retry policy
Volley allows you to define a Retry policy. The toolbox provides a default policy (2500 millisecond timeout, and 1 retry attempt) for you to use or you can pass in your own custom RetryPolicy implementation.
request.setRetryPolicy( new DefaultRetryPolicy());
request.setRetryPolicy(new RetryPolicy() { @Override public int getCurrentTimeout() { return 3000; // 3 seconds } @Override public int getCurrentRetryCount() { return 3; // Attempt to retry at most 3 times } @Override public void retry(VolleyError error) throws VolleyError { // i.e increase retry counter and determine if we reached the maximum retry attempts. If so, throw the error } });
Image Loading
Volley has powerful and customisable image loading capabilities. Let’s see how a simple Image download task can be implemented using HttpUrlConnection.
public class HttpURLConnectionImageAsyncTask extends AsyncTask<String, Void, Bitmap>{ private ImageView mImageView; public HttpURLConnectionImageAsyncTask(ImageView imageView){ this.mImageView = imageView; } @Override protected Bitmap doInBackground(String... strings) { HttpURLConnection connection = null; try { URL imageUrl = new URL(strings[0]); connection = (HttpURLConnection) imageUrl.openConnection(); connection.setReadTimeout(10000); connection.setConnectTimeout(15000); connection.setRequestMethod("GET"); connection.setDoInput(true); connection.connect(); if(connection.getResponseCode() == HttpURLConnection.HTTP_OK){ return BitmapFactory.decodeStream(connection.getInputStream()); } return null; } catch (IOException e) { e.printStackTrace(); }finally { if(connection != null) connection.disconnect(); } return null; } @Override public void onPostExecute(Bitmap bitmap){ if (bitmap != null) mImageView.setImageBitmap(bitmap); } }
mHttpUrlConnectionImageView = (ImageView) findViewById(R.id.httpurlconnection_image_view); new HttpURLConnectionImageAsyncTask(mHttpUrlConnectionImageView).execute(IMAGE_URL);
The network request and image decoding is performed in a worker thread and the Bitmap posted to the UI thread, not affecting the UI thread execution and responsiveness. However, no caching is made and lots of boiler plate code is introduced, regardless of how simple this example is. See try catch blocks, connection setup… imagine if we add caching and a retry and back-off algorithm.
Lets now see how to approach this with Volley. I will add a new default ImageView to the View hierarchy to display images retrieved with Volley.
mVolleyImageView = (ImageView) findViewById(R.id.volley_image_view);
I will use an ImageRequest object that comes built-in the Volley toolbox.
ImageRequest imageRequest = new ImageRequest(IMAGE_URL, new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap response) { mVolleyImageView.setImageBitmap(response); } }, 0, 0, ImageView.ScaleType.CENTER_CROP, null, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // show an error drawable mVolleyImageView.setImageDrawable(getDrawable(R.drawable.error)); } }){ @Override public Priority getPriority(){ return Priority.LOW; } }; mRequestQueue.add(imageRequest);
Regardless of the simplicity of this examples, it’s noticeable how all the boilerplate code is gone and reduced to a couple of callbacks. Also, note that image caching is enabled by default, so no need to add any extra code as you would have for an HttpUrlConnection approach. Handling and maintaining caching using a HashMap or a LruCaches is tedious and too error prone. Volley does all that for ourselves deciding whether to cache response or not, based only on “Cache-Control” and “Expires” HTTP headers.
Note that we also set the Priority of the ImageRequest to LOW – something that you would generally do as we mentioned above.
There is more…
Volley also provides an ImageLoader helper class. You can make use of it if you need to have a more customised image loading implementation. In order to use it you need to have a RequestQueue instantiated and your own ImageCache implementation.
Here is a basic ImageCache that extends the LruCache that comes bundled in the Android SDKs.
public class LruBitmapCache extends LruCache<String, Bitmap> implements ImageLoader.ImageCache { public LruBitmapCache(int maxSize) { super(maxSize); } public LruBitmapCache(Context ctx) { this(getCacheSize(ctx)); } @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } @Override public Bitmap getBitmap(String url) { return get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { put(url, bitmap); } // Returns a cache size equal to approximately three screens worth of images. public static int getCacheSize(Context ctx) { final DisplayMetrics displayMetrics = ctx.getResources(). getDisplayMetrics(); final int screenWidth = displayMetrics.widthPixels; final int screenHeight = displayMetrics.heightPixels; // 4 bytes per pixel final int screenBytes = screenWidth * screenHeight * 4; return screenBytes * 3; } }
On the Java side, use the ImageLoader as follows
mImageLoader = new ImageLoader(mRequestQueue, new LruBitmapCache(LruBitmapCache.getCacheSize(this))); mImageLoader.get(IMAGE_URL, ImageLoader.getImageListener(mVolleyImageLoaderImageView, R.mipmap.ic_launcher, R.drawable.error));
And for super super lazy programmers, the toolbox includes a NetworkImageView that be can directly include in the View hierarchy and then set the image URL from the Java code. The View handles itself the request lifecycle depending on the View state, like when the View gets detached from the View hierarchy the request is cancelled.
<com.android.volley.toolbox.NetworkImageView android:id="@+id/network_image_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="centerCrop"/>
mNetworkImageView = (NetworkImageView) findViewById(R.id.network_image_view); mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader); mNetworkImageView.setDefaultImageResId(R.mipmap.ic_launcher); mNetworkImageView.setErrorImageResId(R.drawable.error);
Wrapping up..
We covered the core features of the Volley library toolbox and compared them with it’s HttpUrlConnection approach. There is a couple more features to go over. Make sure you visit the official docs for further details. https://developer.android.com/training/volley/index.html
It’s worth mentioning that I am not trying to sell you this library above any other library you may find or have used while developing networking in your apps. Nevertheless, I believe it’s extremely positive that Google’s Android engineers put together all their learnt lessons over the years into this single library and be able to take advantage of that in our apps, mainly in those common patterns that are basically full of boilerplate code that is pretty straightforward but extremely tedious to code.
If you would like to review some code please send us a message at Infuy
Definitely Volley is a great alternative for you to take into account when having to dig into networking code in your android app.
Posted in Android, Mobile, React Native, Software Development, Technologies