Volley Request Manager

Full source code on github

On mine 3 years of developing practice every second project had feature, like Http Client or Image Loader, that can be easily done with Volley. Thats why I decided to develop some sort of model that will provide:

  • Easy and reusable interface
  • Possibility to use different queues
  • Background and volley default queues implementations
  • Possibility to create your own queues
  • Factory that will help to create your own queues
  • Callback that handle result in background and deliver result in UI thread
  • Possibility to use default Volley Listeners
  • Load Images with different Image Loaders
  • Factory that will help to create your own Image Loader
  • Possibility to clear Image Loader memory cache

Why I choose Volley, and not OKHttp(or another Http lib) instead? Because it is simple, powerfool, extendable, with built-in memory and disk cache library from Google.

Usage examples :

//Initialize manager
RequestManager.initializeWith(contex);

//Queue using custom listener
RequestManager.queue()
        .useBackgroundQueue()
        .addRequest(new TestJsonRequest(), mRequestCallback)
        .start();

//Queue using default volley Response and Error listener
RequestManager
        .queue()
        .useBackgroundQueue()
        .addRequest(new TestJsonRequest(), mListener, mErrorListener)
        .start();

//load image
 RequestManager
            .loader()
            .useDefaultLoader()
            .obtain()
            .get(
                    "http://farm6.staticflickr.com/5475/10375875123_75ce3080c6_b.jpg",
                    mImageListener
            );

//clear cache
 RequestManager
            .loader()
            .useDefaultLoader()
            .clearCache();        

Queue

Look on this great and easy to understand article from Dmytro Danylyk - Volley Part 2 - Application Model. As he said if you want to control your request and queues from different parts of application, you need to use Singleton.

You can create it like this :

public class RequestManager {

    private static RequestManager instance;
    private RequestQueue mRequestQueue;

    private RequestManager(Context context) {
        mRequestQueue = new Volley().newRequestQueue(context.getApplicationContext());
    }

    public static synchronized RequestQueueBuilder getInstance(Context context) {
        if (instance == null) {
            instance = new RequestManager(context);
        }

        return instance.getRequestBuilder().mQueueBuilder;
    }

    public void doRequest() {
        final Request request = //your request initialization here
        mRequestQueue.add(request);
    }

    //... other requests goes here
}

And it will be great, great until you need to use different queues in your application. You might say that there are no problems, just add new RequestQueue field and initialize it

    private RequestQueue mSecondRequestQueue;

    private RequestManager(Context context) {
        //...
        mSecondRequestQueue = //create your custom Queue here
    }

And what to do when there is more than two or three of them? More fields that will migrate from one project to another?

There is more radical solution - Map.

public class QueueBuilder {

    private Context mContext;

    private Map<String, RequestQueue> mRequestQueue = new HashMap<String, RequestQueue>();

    private String mCurQueue;    

    public QueueBuilder(Context context) {
        mContext = context;
    }

    public RequestController use(String queueName) {
        validateQueue(queueName);
        mCurQueue = queueName;
        return mRequestController;
    }

    private void validateQueue(String queueName) {
        if (!mRequestQueue.containsKey(queueName)) {
            final RequestQueue queue = RequestQueueFactory.getQueue(mContext, queueName);
            if (queue != null) {
                mRequestQueue.put(queueName, queue);
            } else {
                throw new IllegalArgumentException(
                        "RequestQueue - \"" + queueName + "\" doesn't exists!");
            }
        }
    }

}    

And to collect all yours Queues use simple Factory class :

public class RequestQueueFactory {

    public static RequestQueue getQueue(Context context, String name) {
        RequestQueue result = null;

        if (RequestOptions.DEFAULT_QUEUE.equals(name)) {
            result = getDefault(context);
        }
        if (RequestOptions.BACKGROUND_QUEUE.equals(name)) {
            result = newBackgroundQueue(context);
        }

        return result;
    }

    public static RequestQueue getDefault(Context context) {
        return Volley.newRequestQueue(context.getApplicationContext());
    }

    //... all your queue realizations and helpers
}

You can define simple method to use your favorite queue

public class QueueBuilder {
    //...
    public RequestController useBackgroundQueue() {
        return use(RequestOptions.BACKGROUND_QUEUE);
    }    
}

Request

Another ugly problem is Requests creation. If you want to reuse Volley from old project than you will definitely meet it in your new one.

public class RequestManager {
    //...
    //your old Request Manager with old requests.
    public void doRequest() {
        final Request request = //your request initialization here
        mRequestQueue.add(request);
    }

    //to reuse this manager in another project you will have to remove or change your old
    //requests from here
}

To create new\remove old\change current requests you will have to change your RequestManager. This is not the best practice and just not comfortable.

Thats why I decided to encapsulate methods and make their behavior as objects (Strategy design pattern).

public abstract class RequestInterface {
    public abstract Request create();
}

Now we can create controller that will work with Queue and that simple Interface

public class RequestController {

    private QueueBuilder mQueueBuilder;

    public RequestController(Context context) {
        mQueueBuilder = new QueueBuilder(context);
    }

    public RequestController addRequest(RequestInterface volleyRequest) {
        mQueueBuilder.getRequestQueue().add(volleyRequest.create());
        return this;
    }

    public void start() {
        mQueueBuilder.getRequestQueue().start();
    }

    public void stop() {
        mQueueBuilder.getRequestQueue().stop();
    }

    public void cancelAll(Object tag) {
        mQueueBuilder.getRequestQueue().cancelAll(tag);
    }

    public void cancelAll(RequestQueue.RequestFilter requestFilter) {
        mQueueBuilder.getRequestQueue().cancelAll(requestFilter);
    }
}

Simple Request example:

public class TestJsonRequest extends RequestInterface {

    private Response.Listener<JSONObject> mResponseListener;
    private Response.ErrorListener errorListener = mErrorListener;

    public TestJsonRequest(Response.Listener<JSONObject> responseListener,
            Response.ErrorListener errorListener) {
        mResponseListener = responseListener;
        mErrorListener = errorListener;
    }

    @Override
    public Request create() {
        Uri.Builder uri = new Uri.Builder();
        uri.scheme("http");
        uri.authority("httpbin.org");
        uri.path("get");
        uri.appendQueryParameter("name", "Jon Doe");
        uri.appendQueryParameter("age", "21");
        String url = uri.build().toString();

        Request request = new JsonObjectRequest(
                Request.Method.GET,
                url,
                null,
                mResponseListener,
                mErrorListener);

        return request;
    }
}

Background

Volley deliver result from Requests into Callbacks that are triggered in UI thread(more about this you can find here), this is not good especially when you need to parse and/or save Request result.

Thats why I've added default Queue that always deliver result in background thread. But then I've meet another problem - updating UI after background process. To do this we need to trigger runOnUiThread() or use Handler

private Response.Listener mListener = new Response.Listener<JSONObject>() {
    @Override
    public void onResponse(JSONObject o) {
        //parse and save response data

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //update UI here
            }
        });
    }
};

private Response.ErrorListener mErrorListener = new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError volleyError) {
        //handle errors here (UI thread)    
    }
};

I don't want to do this manually, every time when I add new Callback. And that inner Runnable object in inner Listener object looks terrible. So I've created Callback that handle result in background and deliver result in UI thread like AsyncTask does

private RequestCallback mRequestCallback = new RequestCallback<JSONObject, ResultType>() {
    @Override
    public ResultType doInBackground(JSONObject response) {
        //parse and save response data
        return new ResultType();
    }

    @Override
    public void onPostExecute(ResultType result) {
        //update UI here
        Toast.makeText(getApplicationContext(), "Toast from UI", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onError(VolleyError error) {
        //handle errors here (UI thread)
        L.e(error.toString());
    }
};

and updated RequestInterface

public abstract class RequestInterface<ResponseType, ResultType> {

    protected Handler mHandler;
    private RequestCallback<ResponseType, ResultType> mRequestCallback;
    private Response.Listener<ResponseType> mResponseListener;
    private Response.ErrorListener mErrorListener;

    public RequestInterface() {
        mHandler = new Handler(Looper.getMainLooper());
    }

    public abstract Request create();

    private Response.Listener<ResponseType> mInterfaceListener
            = new Response.Listener<ResponseType>() {
        @Override
        public void onResponse(ResponseType response) {
            if (mResponseListener != null) {
                mResponseListener.onResponse(response);
            } else if (mRequestCallback != null) {
                final ResultType resultType = mRequestCallback.doInBackground(response);
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mRequestCallback.onPostExecute(resultType);
                    }
                });
            }
        }
    };

    private Response.ErrorListener mInterfaceErrorListener = new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            if (mErrorListener != null) {
                mErrorListener.onErrorResponse(error);
            } else if (mRequestCallback != null) {
                mRequestCallback.onError(error);
            }
        }
    };

    public final Response.Listener<ResponseType> useInterfaceListener() {
        return mInterfaceListener;
    }

    public final Response.ErrorListener useInterfaceErrorListener() {
        return mInterfaceErrorListener;
    }

    final void setRequestCallback(RequestCallback<ResponseType, ResultType> requestCallback) {
        mRequestCallback = requestCallback;
    }

    final void setResponseListener(Response.Listener<ResponseType> responseListener) {
        mResponseListener = responseListener;
    }

    final void setErrorListener(Response.ErrorListener errorListener) {
        mErrorListener = errorListener;
    }
}

new Request creation will look like this

public class TestJsonRequest extends RequestInterface<JSONObject, Void> {

    @Override
    public Request create() {
        Uri.Builder uri = new Uri.Builder();
        uri.scheme("http");
        uri.authority("httpbin.org");
        uri.path("get");
        uri.appendQueryParameter("name", "Jon Doe");
        uri.appendQueryParameter("age", "21");
        String url = uri.build().toString();

        Request request = new JsonObjectRequest(
                Request.Method.GET,
                url,
                null,

                //if you want to use Callbacks provided
                //via Request Manager interface
                //use useInterfaceListener() and useInterfaceErrorListener()
                //instead of creating new listenets here

                useInterfaceListener(),
                useInterfaceErrorListener());
        return request;
    }
}

To avoid Callbacks initialization via RequestInterface we need to provide a little update for RequestController

public class RequestController {
    //...

    public RequestController addRequest(RequestInterface volleyRequest,
            RequestCallback requestCallback) {
        volleyRequest.setRequestCallback(requestCallback);
        mQueueBuilder.getRequestQueue().add(volleyRequest.create());
        return this;
    }

    public RequestController addRequest(RequestInterface volleyRequest,
            Response.Listener responseListener, Response.ErrorListener errorListener) {
        volleyRequest.setResponseListener(responseListener);
        volleyRequest.setErrorListener(errorListener);
        mQueueBuilder.getRequestQueue().add(volleyRequest.create());
        return this;
    }

    //...
}

Image Loader

Like in Volley Request creation, queues problems are present in Image Loader. So I've added ImageQueueBuilder and ImageLoaderController like I did for Requests. There is only one difference between them - BitmapLruCache interface .

public class ImageLoaderController {

    private ImageQueueBuilder mImageQueueBuilder;

    public ImageLoaderController(Context context) {
        mImageQueueBuilder = new ImageQueueBuilder(context);
    }

    public ImageLoader obtain() {
        return mImageQueueBuilder.getLoader();
    }

    public void clearCache() {
        final BitmapLruCache cache = mImageQueueBuilder.getCache();
        if (cache != null) {
            cache.evictAll();
        }
    }
}

BitmapLruCache is responsible for memory caching. It is pretty useful to be able to release memory and furthermore from large Bitmap.