From bc6beeab59fb8a708c62adc4d14e96b27987665f Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Thu, 1 Apr 2021 08:21:10 -0500 Subject: [PATCH 1/6] initial markdown conversion --- docs/readme.md | 52 ++++++++++ docs/request-custom.md | 174 ++++++++++++++++++++++++++++++++ docs/request-queue.md | 210 +++++++++++++++++++++++++++++++++++++++ docs/request-simple.md | 142 ++++++++++++++++++++++++++ docs/request-standard.md | 63 ++++++++++++ 5 files changed, 641 insertions(+) create mode 100644 docs/readme.md create mode 100644 docs/request-custom.md create mode 100644 docs/request-queue.md create mode 100644 docs/request-simple.md create mode 100644 docs/request-standard.md diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 00000000..015dcd84 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,52 @@ +# Volley Overview + +Volley is an HTTP library that makes networking for Android apps easier and most importantly, faster. Volley is available on [GitHub](https://github.com/google/volley). + +Volley offers the following benefits: +- Automatic scheduling of network requests. +- Multiple concurrent network connections. +- Transparent disk and memory response caching with standard HTTP [cache coherence](https://en.wikipedia.org/wiki/Cache_coherence). +- Support for request prioritization. +- Cancellation request API. You can cancel a single request, or you can set blocks or scopes of requests to cancel. +- Ease of customization, for example, for retry and backoff. +- Strong ordering that makes it easy to correctly populate your UI with data fetched asynchronously from the network. +- Debugging and tracing tools. + +Volley excels at RPC-type operations used to populate a UI, such as fetching a page of search results as structured data. It integrates easily with any protocol and comes out of the box with support for raw strings, images, and JSON. By providing built-in support for the features you need, Volley frees you from writing boilerplate code and allows you to concentrate on the logic that is specific to your app. + +Volley is not suitable for large download or streaming operations, since Volley holds all responses in memory during parsing. For large download operations, consider using an alternative like [DownloadManager.](https://developer.android.com/reference/android/app/DownloadManager) + +The core Volley library is developed on [GitHub](https://github.com/google/volley) and contains the main request dispatch pipeline as well as a set of commonly applicable utilities, available in the Volley "toolbox." The easiest way to add Volley to your project is to add the following dependency to your app's build.gradle file: + +``` +dependencies { + ... + implementation 'com.android.volley:volley:1.2.0' +} +``` + +You can also clone the Volley repository and set it as a library project: + +1. Git clone the repository by typing the following at the command line: + ``` + git clone https://github.com/google/volley + ``` +2. Import the downloaded source into your app project as an Android library module as described in [Create an Android Library](https://developer.android.com/studio/projects/android-library). + +## Lessons + +- [Send a simple request](request-simple.md) + + Learn how to send a simple request using the default behaviors of Volley, and how to cancel a request. + +- [Set up RequestQueue](request-queue.md) + + Learn how to set up a `RequestQueue`, and how to implement a singleton pattern to create a RequestQueue that lasts the lifetime of your app. + +- [Make a standard request](request-standard.md) + + Learn how to send a request using one of Volley's out-of-the-box request types (raw strings, images, and JSON). + +- [Implement a custom request](request-custom.md) + + Learn how to implement a custom request. \ No newline at end of file diff --git a/docs/request-custom.md b/docs/request-custom.md new file mode 100644 index 00000000..50186eb7 --- /dev/null +++ b/docs/request-custom.md @@ -0,0 +1,174 @@ +# Implement a custom request +This lesson describes how to implement your own custom request types, for types that don't have out-of-the-box Volley support. + +## Write a custom request +Most requests have ready-to-use implementations in the toolbox; if your response is a string, image, or JSON, you probably won't need to implement a custom `Request`. + +For cases where you do need to implement a custom request, this is all you need to do: + +- Extend the `Request` class, where `` represents the type of parsed response the request expects. So if your parsed response is a string, for example, create your custom request by extending `Request`. See the Volley toolbox classes `StringRequest` and `ImageRequest` for examples of extending `Request`. +- Implement the abstract methods `parseNetworkResponse()` and `deliverResponse()`, described in more detail below. + +## parseNetworkResponse +A `Response` encapsulates a parsed response for delivery, for a given type (such as string, image, or JSON). Here is a sample implementation of `parseNetworkResponse()`: + +### Kotlin +``` +override fun parseNetworkResponse(response: NetworkResponse?): Response { + return try { + val json = String( + response?.data ?: ByteArray(0), + Charset.forName(HttpHeaderParser.parseCharset(response?.headers))) + Response.success( + gson.fromJson(json, clazz), + HttpHeaderParser.parseCacheHeaders(response)) + } + // handle errors +// ... +} +``` +### Java +``` +@Override +protected Response parseNetworkResponse( + NetworkResponse response) { + try { + String json = new String(response.data, + HttpHeaderParser.parseCharset(response.headers)); + return Response.success(gson.fromJson(json, clazz), + HttpHeaderParser.parseCacheHeaders(response)); + } + // handle errors +// ... +} +``` + +Note the following: +- `parseNetworkResponse()` takes as its parameter a `NetworkResponse`, which contains the response payload as a byte[], HTTP status code, and response headers. +- Your implementation must return a `Response`, which contains your typed response object and cache metadata or an error, such as in the case of a parse failure. + +If your protocol has non-standard cache semantics, you can build a `Cache.Entry` yourself, but most requests are fine with something like this: + +### Kotlin +``` +return Response.success(myDecodedObject, + HttpHeaderParser.parseCacheHeaders(response)) +``` +### Java +``` +return Response.success(myDecodedObject, + HttpHeaderParser.parseCacheHeaders(response)); +``` + +Volley calls `parseNetworkResponse()` from a worker thread. This ensures that expensive parsing operations, such as decoding a JPEG into a Bitmap, don't block the UI thread. + +## deliverResponse +Volley calls you back on the main thread with the object you returned in `parseNetworkResponse()`. Most requests invoke a callback interface here, for example: + +### Kotlin +``` +override fun deliverResponse(response: T) = listener.onResponse(response) +``` +### Java +``` +protected void deliverResponse(T response) { + listener.onResponse(response); +``` + +## Example: GsonRequest +[Gson](https://github.com/google/gson) is a library for converting Java objects to and from JSON using reflection. You can define Java objects that have the same names as their corresponding JSON keys, pass Gson the class object, and Gson will fill in the fields for you. Here's a complete implementation of a Volley request that uses Gson for parsing: + +### Kotlin +``` +/** + * Make a GET request and return a parsed object from JSON. + * + * @param url URL of the request to make + * @param clazz Relevant class object, for Gson's reflection + * @param headers Map of request headers + */ +class GsonRequest( + url: String, + private val clazz: Class, + private val headers: MutableMap?, + private val listener: Response.Listener, + errorListener: Response.ErrorListener +) : Request(Method.GET, url, errorListener) { + private val gson = Gson() + + + override fun getHeaders(): MutableMap = headers ?: super.getHeaders() + + override fun deliverResponse(response: T) = listener.onResponse(response) + + override fun parseNetworkResponse(response: NetworkResponse?): Response { + return try { + val json = String( + response?.data ?: ByteArray(0), + Charset.forName(HttpHeaderParser.parseCharset(response?.headers))) + Response.success( + gson.fromJson(json, clazz), + HttpHeaderParser.parseCacheHeaders(response)) + } catch (e: UnsupportedEncodingException) { + Response.error(ParseError(e)) + } catch (e: JsonSyntaxException) { + Response.error(ParseError(e)) + } + } +} +``` +### Java +``` +public class GsonRequest extends Request { + private final Gson gson = new Gson(); + private final Class clazz; + private final Map headers; + private final Listener listener; + + /** + * Make a GET request and return a parsed object from JSON. + * + * @param url URL of the request to make + * @param clazz Relevant class object, for Gson's reflection + * @param headers Map of request headers + */ + public GsonRequest(String url, Class clazz, Map headers, + Listener listener, ErrorListener errorListener) { + super(Method.GET, url, errorListener); + this.clazz = clazz; + this.headers = headers; + this.listener = listener; + } + + @Override + public Map getHeaders() throws AuthFailureError { + return headers != null ? headers : super.getHeaders(); + } + + @Override + protected void deliverResponse(T response) { + listener.onResponse(response); + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + String json = new String( + response.data, + HttpHeaderParser.parseCharset(response.headers)); + return Response.success( + gson.fromJson(json, clazz), + HttpHeaderParser.parseCacheHeaders(response)); + } catch (UnsupportedEncodingException e) { + return Response.error(new ParseError(e)); + } catch (JsonSyntaxException e) { + return Response.error(new ParseError(e)); + } + } +} +``` + +Volley provides ready-to-use `JsonArrayRequest` and `JsonArrayObject` classes if you prefer to take that approach. See [Make a standard request](request-standard.md) for more information. + +## Previous Lesson +[Make a standard request](request-standard.md) \ No newline at end of file diff --git a/docs/request-queue.md b/docs/request-queue.md new file mode 100644 index 00000000..98c4558b --- /dev/null +++ b/docs/request-queue.md @@ -0,0 +1,210 @@ +# Set up RequestQueue + +The previous lesson showed you how to use the convenience method `Volley.newRequestQueue` to set up a `RequestQueue`, taking advantage of Volley's default behaviors. This lesson walks you through the explicit steps of creating a `RequestQueue`, to allow you to supply your own custom behavior. + +This lesson also describes the recommended practice of creating a `RequestQueue` as a singleton, which makes the `RequestQueue` last the lifetime of your app. + +## Set up a network and cache +A `RequestQueue` needs two things to do its job: a network to perform transport of the requests, and a cache to handle caching. There are standard implementations of these available in the Volley toolbox: `DiskBasedCache` provides a one-file-per-response cache with an in-memory index, and `BasicNetwork` provides a network transport based on your preferred HTTP client. + +`BasicNetwork` is Volley's default network implementation. A BasicNetwork must be initialized with the HTTP client your app is using to connect to the network. Typically this is an [HttpURLConnection](https://developer.android.com/reference/java/net/HttpURLConnection). + +This snippet shows you the steps involved in setting up a `RequestQueue`: + +### Kotlin +``` +// Instantiate the cache +val cache = DiskBasedCache(cacheDir, 1024 * 1024) // 1MB cap + +// Set up the network to use HttpURLConnection as the HTTP client. +val network = BasicNetwork(HurlStack()) + +// Instantiate the RequestQueue with the cache and network. Start the queue. +val requestQueue = RequestQueue(cache, network).apply { + start() +} + +val url = "http://www.example.com" + +// Formulate the request and handle the response. +val stringRequest = StringRequest(Request.Method.GET, url, + Response.Listener { response -> + // Do something with the response + }, + Response.ErrorListener { error -> + // Handle error + textView.text = "ERROR: %s".format(error.toString()) + }) + +// Add the request to the RequestQueue. +requestQueue.add(stringRequest) + +// ... +``` +### Java +``` +RequestQueue requestQueue; + +// Instantiate the cache +Cache cache = new DiskBasedCache(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. +requestQueue = new RequestQueue(cache, network); + +// Start the queue +requestQueue.start(); + +String url ="http://www.example.com"; + +// Formulate the request and handle the response. +StringRequest stringRequest = new StringRequest(Request.Method.GET, url, + new Response.Listener() { + @Override + public void onResponse(String response) { + // Do something with the response + } +}, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + // Handle error + } +}); + +// Add the request to the RequestQueue. +requestQueue.add(stringRequest); + +// ... +``` + +If you just need to make a one-time request and don't want to leave the thread pool around, you can create the `RequestQueue` wherever you need it and call `stop()` on the `RequestQueue` once your response or error has come back, using the `Volley.newRequestQueue()` method described in [Sending a Simple Request](request-simple.md). But the more common use case is to create the `RequestQueue` as a singleton to keep it running for the lifetime of your app, as described in the next section. + +## Use a singleton pattern +If your application makes constant use of the network, it's probably most efficient to set up a single instance of `RequestQueue` that will last the lifetime of your app. You can achieve this in various ways. The recommended approach is to implement a singleton class that encapsulates `RequestQueue` and other Volley functionality. Another approach is to subclass `Application` and set up the `RequestQueue` in `Application.onCreate()`. But this approach is discouraged; a static singleton can provide the same functionality in a more modular way. + +A key concept is that the `RequestQueue` must be instantiated with the `Application` context, not an `Activity` context. This ensures that the `RequestQueue` will last for the lifetime of your app, instead of being recreated every time the activity is recreated (for example, when the user rotates the device). + +Here is an example of a singleton class that provides `RequestQueue` and `ImageLoader` functionality: + +### Kotlin +``` +class MySingleton constructor(context: Context) { + companion object { + @Volatile + private var INSTANCE: MySingleton? = null + fun getInstance(context: Context) = + INSTANCE ?: synchronized(this) { + INSTANCE ?: MySingleton(context).also { + INSTANCE = it + } + } + } + val imageLoader: ImageLoader by lazy { + ImageLoader(requestQueue, + object : ImageLoader.ImageCache { + private val cache = LruCache(20) + override fun getBitmap(url: String): Bitmap { + return cache.get(url) + } + override fun putBitmap(url: String, bitmap: Bitmap) { + cache.put(url, bitmap) + } + }) + } + val requestQueue: RequestQueue by lazy { + // applicationContext is key, it keeps you from leaking the + // Activity or BroadcastReceiver if someone passes one in. + Volley.newRequestQueue(context.applicationContext) + } + fun addToRequestQueue(req: Request) { + requestQueue.add(req) + } +} +``` +### Java +``` +public class MySingleton { + private static MySingleton instance; + private RequestQueue requestQueue; + private ImageLoader imageLoader; + private static Context ctx; + + private MySingleton(Context context) { + ctx = context; + requestQueue = getRequestQueue(); + + imageLoader = new ImageLoader(requestQueue, + new ImageLoader.ImageCache() { + private final LruCache + cache = new LruCache(20); + + @Override + public Bitmap getBitmap(String url) { + return cache.get(url); + } + + @Override + public void putBitmap(String url, Bitmap bitmap) { + cache.put(url, bitmap); + } + }); + } + + public static synchronized MySingleton getInstance(Context context) { + if (instance == null) { + instance = new MySingleton(context); + } + return instance; + } + + public RequestQueue getRequestQueue() { + if (requestQueue == null) { + // getApplicationContext() is key, it keeps you from leaking the + // Activity or BroadcastReceiver if someone passes one in. + requestQueue = Volley.newRequestQueue(ctx.getApplicationContext()); + } + return requestQueue; + } + + public void addToRequestQueue(Request req) { + getRequestQueue().add(req); + } + + public ImageLoader getImageLoader() { + return imageLoader; + } +} +``` + +Here are some examples of performing `RequestQueue` operations using the singleton class: + +### Kotlin +``` +// Get a RequestQueue +val queue = MySingleton.getInstance(this.applicationContext).requestQueue + +// ... + +// Add a request (in this example, called stringRequest) to your RequestQueue. +MySingleton.getInstance(this).addToRequestQueue(stringRequest) +``` +### Java +``` +// Get a RequestQueue +RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()). + getRequestQueue(); + +// ... + +// Add a request (in this example, called stringRequest) to your RequestQueue. +MySingleton.getInstance(this).addToRequestQueue(stringRequest); +``` + +## Previous Lesson +[Sending a Simple Request](request-simple.md) + +## Next Lesson +[Make a standard request](request-standard.md) diff --git a/docs/request-simple.md b/docs/request-simple.md new file mode 100644 index 00000000..21bd7dd3 --- /dev/null +++ b/docs/request-simple.md @@ -0,0 +1,142 @@ +# Send a simple request + +At a high level, you use Volley by creating a `RequestQueue` and passing it `Request` objects. The `RequestQueue` manages worker threads for running the network operations, reading from and writing to the cache, and parsing responses. Requests do the parsing of raw responses and Volley takes care of dispatching the parsed response back to the main thread for delivery. + +This lesson describes how to send a request using the `Volley.newRequestQueue` convenience method, which sets up a RequestQueue for you. See the next lesson, [Setting Up a RequestQueue](request-queue.md), for information on how to set up a RequestQueue yourself. + +This lesson also describes how to add a request to a `RequestQueue` and cancel a request. + +## Add the INTERNET permission +To use Volley, you must add the [android.permission.INTERNET](https://developer.android.com/reference/android/Manifest.permission#INTERNET) permission to your app's manifest. Without this, your app won't be able to connect to the network. + +## Use newRequestQueue + +Volley provides a convenience method `Volley.newRequestQueue` that sets up a `RequestQueue` for you, using default values, and starts the queue. For example: + +### Kotlin +``` +val textView = findViewById(R.id.text) +// ... + +// Instantiate the RequestQueue. +val queue = Volley.newRequestQueue(this) +val url = "https://www.google.com" + +// Request a string response from the provided URL. +val stringRequest = StringRequest(Request.Method.GET, url, + Response.Listener { response -> + // Display the first 500 characters of the response string. + textView.text = "Response is: ${response.substring(0, 500)}" + }, + Response.ErrorListener { textView.text = "That didn't work!" }) + +// Add the request to the RequestQueue. +queue.add(stringRequest) +``` + +### Java +``` +final TextView textView = (TextView) findViewById(R.id.text); +// ... + +// Instantiate the RequestQueue. +RequestQueue queue = Volley.newRequestQueue(this); +String url ="https://www.google.com"; + +// Request a string response from the provided URL. +StringRequest stringRequest = new StringRequest(Request.Method.GET, url, + new Response.Listener() { + @Override + public void onResponse(String response) { + // Display the first 500 characters of the response string. + textView.setText("Response is: "+ response.substring(0,500)); + } +}, new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + textView.setText("That didn't work!"); + } +}); + +// Add the request to the RequestQueue. +queue.add(stringRequest); +``` + +Volley always delivers parsed responses on the main thread. Running on the main thread is convenient for populating UI controls with received data, as you can freely modify UI controls directly from your response handler, but it's especially critical to many of the important semantics provided by the library, particularly related to canceling requests. + +See [Setting Up a RequestQueue](request-queue.md) for a description of how to set up a RequestQueue yourself, instead of using the Volley.newRequestQueue convenience method. + +## Send a request + +To send a request, you simply construct one and add it to the `RequestQueue` with `add()`, as shown above. Once you add the request it moves through the pipeline, gets serviced, and has its raw response parsed and delivered. + +When you call `add()`, Volley runs one cache processing thread and a pool of network dispatch threads. When you add a request to the queue, it is picked up by the cache thread and triaged: if the request can be serviced from cache, the cached response is parsed on the cache thread and the parsed response is delivered on the main thread. If the request cannot be serviced from cache, it is placed on the network queue. The first available network thread takes the request from the queue, performs the HTTP transaction, parses the response on the worker thread, writes the response to cache, and posts the parsed response back to the main thread for delivery. + +Note that expensive operations like blocking I/O and parsing/decoding are done on worker threads. You can add a request from any thread, but responses are always delivered on the main thread. + +Figure 1 illustrates the life of a request: + +![Figure 1. Life of a request](https://developer.android.com/images/training/volley-request.png?authuser=1) + +## Cancel a request + +To cancel a request, call `cancel()` on your `Request` object. Once cancelled, Volley guarantees that your response handler will never be called. What this means in practice is that you can cancel all of your pending requests in your activity's `onStop()` method and you don't have to litter your response handlers with checks for `getActivity() == null`, whether `onSaveInstanceState()` has been called already, or other defensive boilerplate. + +To take advantage of this behavior, you would typically have to track all in-flight requests in order to be able to cancel them at the appropriate time. There is an easier way: you can associate a tag object with each request. You can then use this tag to provide a scope of requests to cancel. For example, you can tag all of your requests with the `Activity` they are being made on behalf of, and call `requestQueue.cancelAll(this)` from `onStop()`. Similarly, you could tag all thumbnail image requests in a `ViewPager` tab with their respective tabs and cancel on swipe to make sure that the new tab isn't being held up by requests from another one. + +Here is an example that uses a string value for the tag: + +1. Define your tag and add it to your requests. + +### Kotlin +``` +val TAG = "MyTag" +val stringRequest: StringRequest // Assume this exists. +val requestQueue: RequestQueue? // Assume this exists. + +// Set the tag on the request. +stringRequest.tag = TAG + +// Add the request to the RequestQueue. +requestQueue?.add(stringRequest) +``` + +### Java +``` +public static final String TAG = "MyTag"; +StringRequest stringRequest; // Assume this exists. +RequestQueue requestQueue; // Assume this exists. + +// Set the tag on the request. +stringRequest.setTag(TAG); + +// Add the request to the RequestQueue. +requestQueue.add(stringRequest); +``` + +2. In your activity's onStop() method, cancel all requests that have this tag. + +### Kotlin +``` +protected fun onStop() { + super.onStop() + requestQueue?.cancelAll(TAG) +} +``` + +### Java +``` +@Override +protected void onStop () { + super.onStop(); + if (requestQueue != null) { + requestQueue.cancelAll(TAG); + } +} +``` + +Take care when canceling requests. If you are depending on your response handler to advance a state or kick off another process, you need to account for this. Again, the response handler will not be called. + +## Next Lesson + +[Set up RequestQueue](request-queue.md) diff --git a/docs/request-standard.md b/docs/request-standard.md new file mode 100644 index 00000000..dc08d4a3 --- /dev/null +++ b/docs/request-standard.md @@ -0,0 +1,63 @@ +# Make a standard request +This lesson describes how to use the common request types that Volley supports: + +- `StringRequest`. Specify a URL and receive a raw string in response. See [Setting Up a Request Queue](request-queue.md) for an example. +- `JsonObjectRequest` and `JsonArrayRequest` (both subclasses of `JsonRequest`). Specify a URL and get a JSON object or array (respectively) in response. + +If your expected response is one of these types, you probably don't have to implement a custom request. This lesson describes how to use these standard request types. For information on how to implement your own custom request, see [Implementing a Custom Request](request-custom.md). + +## Request JSON +Volley provides the following classes for JSON requests: + +- `JsonArrayRequest` — A request for retrieving a [JSONArray](https://developer.android.com/reference/org/json/JSONArray) response body at a given URL. +- `JsonObjectRequest` — A request for retrieving a [JSONObject](https://developer.android.com/reference/org/json/JSONObject) response body at a given URL, allowing for an optional [JSONObject](https://developer.android.com/reference/org/json/JSONObject) to be passed in as part of the request body. + +Both classes are based on the common base class `JsonRequest`. You use them following the same basic pattern you use for other types of requests. For example, this snippet fetches a JSON feed and displays it as text in the UI: + +### Kotlin +``` +val url = "http://my-json-feed" + +val jsonObjectRequest = JsonObjectRequest(Request.Method.GET, url, null, + Response.Listener { response -> + textView.text = "Response: %s".format(response.toString()) + }, + Response.ErrorListener { error -> + // TODO: Handle error + } +) + +// Access the RequestQueue through your singleton class. +MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest) +``` +### Java +``` +String url = "http://my-json-feed"; + +JsonObjectRequest jsonObjectRequest = new JsonObjectRequest + (Request.Method.GET, url, null, new Response.Listener() { + + @Override + public void onResponse(JSONObject response) { + textView.setText("Response: " + response.toString()); + } +}, new Response.ErrorListener() { + + @Override + public void onErrorResponse(VolleyError error) { + // TODO: Handle error + + } +}); + +// Access the RequestQueue through your singleton class. +MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest); +``` + +For an example of implementing a custom JSON request based on [Gson](https://github.com/google/gson), see the next lesson, [Implement a custom request](request-custom.md). + +## Previous Lesson +[Set up RequestQueue](request-queue.md) + +## Next Lesson +[Implement a custom request](request-custom.md) \ No newline at end of file From d071977bd6557b00cc5defe5373c56d576a41ed4 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Thu, 1 Apr 2021 15:26:58 -0500 Subject: [PATCH 2/6] revised documentation --- docs/readme.md | 28 ++-- docs/request-custom.md | 226 +++++++++++++++++------------- docs/request-queue.md | 293 ++++++++++++++++++++------------------- docs/request-simple.md | 190 +++++++++++++------------ docs/request-standard.md | 185 +++++++++++++++++++----- 5 files changed, 540 insertions(+), 382 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 015dcd84..ea520f71 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,11 +1,11 @@ # Volley Overview -Volley is an HTTP library that makes networking for Android apps easier and most importantly, faster. Volley is available on [GitHub](https://github.com/google/volley). +Volley is an HTTP library that makes networking for Android apps easier and most importantly, faster. Volley offers the following benefits: - Automatic scheduling of network requests. - Multiple concurrent network connections. -- Transparent disk and memory response caching with standard HTTP [cache coherence](https://en.wikipedia.org/wiki/Cache_coherence). +- Transparent disk and memory response caching with standard [HTTP Cache Coherence.](https://en.wikipedia.org/wiki/Cache_coherence) - Support for request prioritization. - Cancellation request API. You can cancel a single request, or you can set blocks or scopes of requests to cancel. - Ease of customization, for example, for retry and backoff. @@ -16,7 +16,7 @@ Volley excels at RPC-type operations used to populate a UI, such as fetching a p Volley is not suitable for large download or streaming operations, since Volley holds all responses in memory during parsing. For large download operations, consider using an alternative like [DownloadManager.](https://developer.android.com/reference/android/app/DownloadManager) -The core Volley library is developed on [GitHub](https://github.com/google/volley) and contains the main request dispatch pipeline as well as a set of commonly applicable utilities, available in the Volley "toolbox." The easiest way to add Volley to your project is to add the following dependency to your app's build.gradle file: +The core Volley library is developed on [GitHub](https://github.com/google/volley) and contains the main request dispatch pipeline as well as a set of commonly applicable utilities, available in the Volley "toolbox." The easiest way to add Volley to your project is to add the following dependency to your app's `build.gradle` file: ``` dependencies { @@ -25,28 +25,20 @@ dependencies { } ``` -You can also clone the Volley repository and set it as a library project: - -1. Git clone the repository by typing the following at the command line: - ``` - git clone https://github.com/google/volley - ``` -2. Import the downloaded source into your app project as an Android library module as described in [Create an Android Library](https://developer.android.com/studio/projects/android-library). - ## Lessons - [Send a simple request](request-simple.md) - Learn how to send a simple request using the default behaviors of Volley, and how to cancel a request. - -- [Set up RequestQueue](request-queue.md) - - Learn how to set up a `RequestQueue`, and how to implement a singleton pattern to create a RequestQueue that lasts the lifetime of your app. + Learn how to send a simple request using Volley, and how to cancel a request. - [Make a standard request](request-standard.md) - Learn how to send a request using one of Volley's out-of-the-box request types (raw strings, images, and JSON). + Learn how to send a request using one of Volley's out-of-the-box request types (raw strings, images, and org.json). - [Implement a custom request](request-custom.md) - Learn how to implement a custom request. \ No newline at end of file + Learn how to implement a custom request. + +- [Setting up a RequestQueue](request-queue.md) + + Learn how to customize your `RequestQueue`. diff --git a/docs/request-custom.md b/docs/request-custom.md index 50186eb7..ea8e6a05 100644 --- a/docs/request-custom.md +++ b/docs/request-custom.md @@ -1,78 +1,77 @@ # Implement a custom request -This lesson describes how to implement your own custom request types, for types that don't have out-of-the-box Volley support. +This lesson describes how to implement your own custom request types for data types, parsers, and other functionality which Volley does not support out-of-the-box. -## Write a custom request -Most requests have ready-to-use implementations in the toolbox; if your response is a string, image, or JSON, you probably won't need to implement a custom `Request`. +Implementing custom request types will allow you to implement the following common use cases: +- Use your preferred JSON formatter/parser such as [Gson](https://github.com/google/gson), [Moshi](https://github.com/square/moshi), or [Jackson](https://github.com/FasterXML/jackson). +- Send and parse XML data. +- Send and parse binary data. +- Add request headers such as [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type), [Authorization](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization), [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept), etc. +- Send POST request bodies such as `multipart/form-data`, JSON, XML, plain text, etc. -For cases where you do need to implement a custom request, this is all you need to do: +## Extend the Request class +To implement a custom request, this is what you need to do: -- Extend the `Request` class, where `` represents the type of parsed response the request expects. So if your parsed response is a string, for example, create your custom request by extending `Request`. See the Volley toolbox classes `StringRequest` and `ImageRequest` for examples of extending `Request`. +- Extend the `Request` class, where `` represents the type of parsed response that you expect. - Implement the abstract methods `parseNetworkResponse()` and `deliverResponse()`, described in more detail below. -## parseNetworkResponse +*See the `StringRequest`, `JsonObjectRequest`, `JsonRequest`, and `ImageRequest` toolbox classes for more examples of extending `Request`.* + +## Implement parseNetworkResponse() A `Response` encapsulates a parsed response for delivery, for a given type (such as string, image, or JSON). Here is a sample implementation of `parseNetworkResponse()`: ### Kotlin ``` -override fun parseNetworkResponse(response: NetworkResponse?): Response { - return try { - val json = String( - response?.data ?: ByteArray(0), - Charset.forName(HttpHeaderParser.parseCharset(response?.headers))) - Response.success( - gson.fromJson(json, clazz), - HttpHeaderParser.parseCacheHeaders(response)) - } - // handle errors -// ... +override fun parseNetworkResponse(response: NetworkResponse): Response { + try { + val charset = HttpHeaderParser.parseCharset(response.headers, "utf-8"); + val json = String(response.data, charset) + val obj = gson.fromJson(json, User.class); + + val cacheEntry = HttpHeaderParser.parseCacheHeaders(response); + return Response.success(obj, cacheEntry); + } catch (ex: Exception) { + return Response.error(new ParseError(ex)); + } } ``` ### Java ``` @Override -protected Response parseNetworkResponse( - NetworkResponse response) { - try { - String json = new String(response.data, - HttpHeaderParser.parseCharset(response.headers)); - return Response.success(gson.fromJson(json, clazz), - HttpHeaderParser.parseCacheHeaders(response)); - } - // handle errors -// ... +protected Response parseNetworkResponse(NetworkResponse response) { + try { + String charset = HttpHeaderParser.parseCharset(response.headers, "utf-8"); + String json = new String(response.data, charset); + User obj = gson.fromJson(json, User.class); + + Cache.Entry cacheEntry = HttpHeaderParser.parseCacheHeaders(response); + return Response.success(obj, cacheEntry); + } catch (Exception ex) { + return Response.error(new ParseError(ex)); + } } ``` Note the following: -- `parseNetworkResponse()` takes as its parameter a `NetworkResponse`, which contains the response payload as a byte[], HTTP status code, and response headers. +- `parseNetworkResponse()` takes as its parameter a `NetworkResponse`, which contains the response payload as a byte[], the HTTP status code, and the response headers. - Your implementation must return a `Response`, which contains your typed response object and cache metadata or an error, such as in the case of a parse failure. +- If your protocol has non-standard cache semantics, you can build a `Cache.Entry` yourself, but most requests will be fine with the `HttpHeaderParser.parseCacheHeaders()` utility method shown above. +- Volley calls `parseNetworkResponse()` from a worker thread. This ensures that expensive decoding operations, such as parsing JSON/XML, or decoding a JPEG into a Bitmap, don't block the UI thread. -If your protocol has non-standard cache semantics, you can build a `Cache.Entry` yourself, but most requests are fine with something like this: - -### Kotlin -``` -return Response.success(myDecodedObject, - HttpHeaderParser.parseCacheHeaders(response)) -``` -### Java -``` -return Response.success(myDecodedObject, - HttpHeaderParser.parseCacheHeaders(response)); -``` - -Volley calls `parseNetworkResponse()` from a worker thread. This ensures that expensive parsing operations, such as decoding a JPEG into a Bitmap, don't block the UI thread. - -## deliverResponse -Volley calls you back on the main thread with the object you returned in `parseNetworkResponse()`. Most requests invoke a callback interface here, for example: +## Implement deliverResponse() +Volley calls you back on the main thread with the parsed object you returned in `parseNetworkResponse()`. You should invoke a callback function here, for example: ### Kotlin ``` -override fun deliverResponse(response: T) = listener.onResponse(response) +override fun deliverResponse(response: User) = listener?.onResponse(response) ``` ### Java ``` -protected void deliverResponse(T response) { - listener.onResponse(response); +@Override +protected void deliverResponse(User response) { + if (listener != null) { + listener.onResponse(response); + } +} ``` ## Example: GsonRequest @@ -80,41 +79,48 @@ protected void deliverResponse(T response) { ### Kotlin ``` -/** - * Make a GET request and return a parsed object from JSON. - * - * @param url URL of the request to make - * @param clazz Relevant class object, for Gson's reflection - * @param headers Map of request headers - */ class GsonRequest( - url: String, - private val clazz: Class, - private val headers: MutableMap?, - private val listener: Response.Listener, - errorListener: Response.ErrorListener -) : Request(Method.GET, url, errorListener) { - private val gson = Gson() + int method, + url: String, + private val clazz: Class, + private val headers: MutableMap?, + private val requestBody: Object?, + private val listener: Response.Listener?, + errorListener: Response.ErrorListener? +) : Request(method, url, errorListener) { + private val gson = Gson() override fun getHeaders(): MutableMap = headers ?: super.getHeaders() - override fun deliverResponse(response: T) = listener.onResponse(response) + override fun getBodyContentType(): String = "application/json; charset=utf-8" + + override fun getBody(): byte[] { + try { + if (requestBody != null) { + return gson.toJson(requestBody).getBytes("utf-8") + } else { + return null; + } + } catch (ex: Exception) { + // handle error ... + } + } override fun parseNetworkResponse(response: NetworkResponse?): Response { - return try { - val json = String( - response?.data ?: ByteArray(0), - Charset.forName(HttpHeaderParser.parseCharset(response?.headers))) - Response.success( - gson.fromJson(json, clazz), - HttpHeaderParser.parseCacheHeaders(response)) - } catch (e: UnsupportedEncodingException) { - Response.error(ParseError(e)) - } catch (e: JsonSyntaxException) { - Response.error(ParseError(e)) + try { + val charset = HttpHeaderParser.parseCharset(response.headers, "utf-8"); + val json = String(response.data, charset) + val obj = gson.fromJson(json, clazz); + + val cacheEntry = HttpHeaderParser.parseCacheHeaders(response); + return Response.success(obj, cacheEntry); + } catch (ex: Exception) { + return Response.error(new ParseError(ex)); } } + + override fun deliverResponse(response: T) = listener?.onResponse(response) } ``` ### Java @@ -123,52 +129,84 @@ public class GsonRequest extends Request { private final Gson gson = new Gson(); private final Class clazz; private final Map headers; + private final Object requestBody; private final Listener listener; /** * Make a GET request and return a parsed object from JSON. * - * @param url URL of the request to make + * @param method the HTTP method to use + * @param url URL to fetch the JSON from * @param clazz Relevant class object, for Gson's reflection * @param headers Map of request headers + * @param requestBody JSON data to be posted as the body of the request. + * Or null to skip the request body. + * @param listener Listener to receive the parsed response + * @param errorListener Error listener, or null to ignore errors */ - public GsonRequest(String url, Class clazz, Map headers, - Listener listener, ErrorListener errorListener) { - super(Method.GET, url, errorListener); + public GsonRequest( + int method, + String url, + Class clazz, + @Nullable Map headers, + @Nullable Object requestBody, + @Nullable Listener listener, + @Nullable ErrorListener errorListener + ) { + super(method, url, errorListener); this.clazz = clazz; this.headers = headers; this.listener = listener; } @Override - public Map getHeaders() throws AuthFailureError { + public Map getHeaders() { return headers != null ? headers : super.getHeaders(); } @Override - protected void deliverResponse(T response) { - listener.onResponse(response); + public String getBodyContentType() { + return "application/json; charset=utf-8"; + } + + @Override + public byte[] getBody() { + try { + if (requestBody != null) { + return gson.toJson(requestBody).getBytes("utf-8"); + } else { + return null; + } + } catch (Exception ex) { + // handle error ... + } } @Override protected Response parseNetworkResponse(NetworkResponse response) { try { - String json = new String( - response.data, - HttpHeaderParser.parseCharset(response.headers)); - return Response.success( - gson.fromJson(json, clazz), - HttpHeaderParser.parseCacheHeaders(response)); - } catch (UnsupportedEncodingException e) { - return Response.error(new ParseError(e)); - } catch (JsonSyntaxException e) { - return Response.error(new ParseError(e)); + String charset = HttpHeaderParser.parseCharset(response.headers, "utf-8"); + String json = new String(response.data, charset); + T obj = gson.fromJson(json, clazz); + + Cache.Entry cacheEntry = HttpHeaderParser.parseCacheHeaders(response); + return Response.success(obj, cacheEntry); + } catch (Exception ex) { + return Response.error(new ParseError(ex)); + } + } + + @Override + protected void deliverResponse(T response) { + if (listener != null) { + listener.onResponse(response); } } } ``` -Volley provides ready-to-use `JsonArrayRequest` and `JsonArrayObject` classes if you prefer to take that approach. See [Make a standard request](request-standard.md) for more information. - ## Previous Lesson -[Make a standard request](request-standard.md) \ No newline at end of file +[Make a standard request](request-standard.md) + +## Next Lesson +[Setting up a RequestQueue](request-queue.md) diff --git a/docs/request-queue.md b/docs/request-queue.md index 98c4558b..860f0510 100644 --- a/docs/request-queue.md +++ b/docs/request-queue.md @@ -1,210 +1,217 @@ -# Set up RequestQueue +# Setting up a RequestQueue -The previous lesson showed you how to use the convenience method `Volley.newRequestQueue` to set up a `RequestQueue`, taking advantage of Volley's default behaviors. This lesson walks you through the explicit steps of creating a `RequestQueue`, to allow you to supply your own custom behavior. +This lesson walks you through the process of configuring the `RequestQueue` to supply your own custom behavior, and discusses options for managing/sharing your `RequestQueue` instances. -This lesson also describes the recommended practice of creating a `RequestQueue` as a singleton, which makes the `RequestQueue` last the lifetime of your app. +## Setup a network and cache +A `RequestQueue` needs two things to do its job: a network to perform transport of the requests, and a cache to handle caching. There are standard implementations of these available in the Volley toolbox: [DiskBasedCache](https://github.com/google/volley/blob/master/src/main/java/com/android/volley/toolbox/DiskBasedCache.java) provides a one-file-per-response cache with an in-memory index, and [BasicNetwork](https://github.com/google/volley/blob/master/src/main/java/com/android/volley/toolbox/BasicNetwork.java) provides a network transport based on your preferred HTTP client. -## Set up a network and cache -A `RequestQueue` needs two things to do its job: a network to perform transport of the requests, and a cache to handle caching. There are standard implementations of these available in the Volley toolbox: `DiskBasedCache` provides a one-file-per-response cache with an in-memory index, and `BasicNetwork` provides a network transport based on your preferred HTTP client. +`BasicNetwork` is Volley's default network implementation. A BasicNetwork must be initialized with the HTTP client your app is using to connect to the network. Typically this is an [HttpURLConnection](https://developer.android.com/reference/java/net/HttpURLConnection), but other HTTP clients are supported (such as [OkHttpStack](https://gist.github.com/JakeWharton/5616899).) -`BasicNetwork` is Volley's default network implementation. A BasicNetwork must be initialized with the HTTP client your app is using to connect to the network. Typically this is an [HttpURLConnection](https://developer.android.com/reference/java/net/HttpURLConnection). - -This snippet shows you the steps involved in setting up a `RequestQueue`: +This snippet shows you the steps involved in configuring a `RequestQueue`: ### Kotlin ``` // Instantiate the cache -val cache = DiskBasedCache(cacheDir, 1024 * 1024) // 1MB cap +val cache = DiskBasedCache(context.getCacheDir(), 1024 * 1024) // 1MB cap -// Set up the network to use HttpURLConnection as the HTTP client. +// Use HttpURLConnection as the HTTP client val network = BasicNetwork(HurlStack()) -// Instantiate the RequestQueue with the cache and network. Start the queue. -val requestQueue = RequestQueue(cache, network).apply { - start() -} - -val url = "http://www.example.com" - -// Formulate the request and handle the response. -val stringRequest = StringRequest(Request.Method.GET, url, - Response.Listener { response -> - // Do something with the response - }, - Response.ErrorListener { error -> - // Handle error - textView.text = "ERROR: %s".format(error.toString()) - }) +// Instantiate the RequestQueue +val requestQueue = RequestQueue(cache, network) -// Add the request to the RequestQueue. -requestQueue.add(stringRequest) - -// ... +// Start the queue +requestQueue.start() ``` ### Java ``` -RequestQueue requestQueue; - // Instantiate the cache -Cache cache = new DiskBasedCache(getCacheDir(), 1024 * 1024); // 1MB cap +Cache cache = new DiskBasedCache(context.getCacheDir(), 1024 * 1024); // 1MB cap -// Set up the network to use HttpURLConnection as the HTTP client. +// Use HttpURLConnection as the HTTP client Network network = new BasicNetwork(new HurlStack()); -// Instantiate the RequestQueue with the cache and network. -requestQueue = new RequestQueue(cache, network); +// Instantiate the RequestQueue +RequestQueue requestQueue = new RequestQueue(cache, network); // Start the queue requestQueue.start(); - -String url ="http://www.example.com"; - -// Formulate the request and handle the response. -StringRequest stringRequest = new StringRequest(Request.Method.GET, url, - new Response.Listener() { - @Override - public void onResponse(String response) { - // Do something with the response - } -}, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - // Handle error - } -}); - -// Add the request to the RequestQueue. -requestQueue.add(stringRequest); - -// ... ``` -If you just need to make a one-time request and don't want to leave the thread pool around, you can create the `RequestQueue` wherever you need it and call `stop()` on the `RequestQueue` once your response or error has come back, using the `Volley.newRequestQueue()` method described in [Sending a Simple Request](request-simple.md). But the more common use case is to create the `RequestQueue` as a singleton to keep it running for the lifetime of your app, as described in the next section. +## Managing RequestQueue Instances +There are three common patterns for managing the instances of `RequestQueue`: -## Use a singleton pattern -If your application makes constant use of the network, it's probably most efficient to set up a single instance of `RequestQueue` that will last the lifetime of your app. You can achieve this in various ways. The recommended approach is to implement a singleton class that encapsulates `RequestQueue` and other Volley functionality. Another approach is to subclass `Application` and set up the `RequestQueue` in `Application.onCreate()`. But this approach is discouraged; a static singleton can provide the same functionality in a more modular way. +- Each `Activity`/`Service`/`Worker` manages their own `RequestQueue`. The `RequestQueue` is created in `onCreate()` and disposed of in `onDestroy()`. This is the easiest to implement and works fine for small projects. It does not prevent multiple `Requests` from executing simultaneously, but is generally sufficient if you do not need to make network requests while the app is asleep. +- The `Application` manages the shared `RequestQueue`. This ensures that there is only one `RequestQueue` instance and prevents multiple `Requests` from being in-flight at the same time. +- If you are using the [Android Architecture Components](https://developer.android.com/topic/libraries/architecture), then the [Repositories](https://developer.android.com/jetpack/guide#recommended-app-arch)/[DataSources](https://developer.android.com/topic/libraries/architecture/paging/data)/[ViewModels](https://developer.android.com/topic/libraries/architecture/viewmodel) can manage the `RequestQueue` instances. +- Using Singletons is **strongly discouraged.** + - [So Singletons are bad, then what?](https://softwareengineering.stackexchange.com/questions/40373/so-singletons-are-bad-then-what) -A key concept is that the `RequestQueue` must be instantiated with the `Application` context, not an `Activity` context. This ensures that the `RequestQueue` will last for the lifetime of your app, instead of being recreated every time the activity is recreated (for example, when the user rotates the device). -Here is an example of a singleton class that provides `RequestQueue` and `ImageLoader` functionality: +## RequestQueue managed by Activity +See the code sample below for an `Activity` which manages its own `RequestQueue`: ### Kotlin ``` -class MySingleton constructor(context: Context) { - companion object { - @Volatile - private var INSTANCE: MySingleton? = null - fun getInstance(context: Context) = - INSTANCE ?: synchronized(this) { - INSTANCE ?: MySingleton(context).also { - INSTANCE = it - } - } - } - val imageLoader: ImageLoader by lazy { - ImageLoader(requestQueue, - object : ImageLoader.ImageCache { - private val cache = LruCache(20) - override fun getBitmap(url: String): Bitmap { - return cache.get(url) - } - override fun putBitmap(url: String, bitmap: Bitmap) { - cache.put(url, bitmap) - } - }) - } - val requestQueue: RequestQueue by lazy { - // applicationContext is key, it keeps you from leaking the - // Activity or BroadcastReceiver if someone passes one in. - Volley.newRequestQueue(context.applicationContext) - } - fun addToRequestQueue(req: Request) { - requestQueue.add(req) +class MyActivity: AppCompatActivity { + ... + private val requestQueue: RequestQueue + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + ... + requestQueue = Volley.newRequestQueue(this) + } + + override fun onDestroy() { + super.onDestroy() + requestQueue?.stop() } } ``` ### Java ``` -public class MySingleton { - private static MySingleton instance; +public class MyActivity extends AppCompatActivity { + ... private RequestQueue requestQueue; - private ImageLoader imageLoader; - private static Context ctx; - private MySingleton(Context context) { - ctx = context; - requestQueue = getRequestQueue(); + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ... + requestQueue = Volley.newRequestQueue(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (requestQueue != null) { + requestQueue.stop(); + } + } +} +``` - imageLoader = new ImageLoader(requestQueue, - new ImageLoader.ImageCache() { - private final LruCache - cache = new LruCache(20); +## RequestQueue managed by Application +See the code sample below for an `Application` which creates and holds a `RequestQueue` that can be shared between all application components: - @Override - public Bitmap getBitmap(String url) { - return cache.get(url); - } +### Kotlin +``` +class MyApplication: Application { + ... + private val requestQueue: RequestQueue - @Override - public void putBitmap(String url, Bitmap bitmap) { - cache.put(url, bitmap); - } - }); + override fun onCreate() { + super.onCreate() + requestQueue = Volley.newRequestQueue(this) } - public static synchronized MySingleton getInstance(Context context) { - if (instance == null) { - instance = new MySingleton(context); - } - return instance; + fun getRequestQueue(): RequestQueue = requestQueue +} +``` +### Java +``` +public class MyApplication extends Application { + private RequestQueue requestQueue; + + @Override + protected void onCreate() { + super.onCreate(); + requestQueue = Volley.newRequestQueue(this); } public RequestQueue getRequestQueue() { - if (requestQueue == null) { - // getApplicationContext() is key, it keeps you from leaking the - // Activity or BroadcastReceiver if someone passes one in. - requestQueue = Volley.newRequestQueue(ctx.getApplicationContext()); - } return requestQueue; } +} +``` + +And below is a sample `Activity` which uses it: - public void addToRequestQueue(Request req) { - getRequestQueue().add(req); +### Kotlin +``` +class MyActivity: AppCompatActivity { + ... + private val app: MyApplication + private val requestQueue: RequestQueue + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + ... + app = getApplication() as MyApplication + requestQueue = Volley.newRequestQueue(this) } +} +``` +### Java +``` +public class MyActivity extends AppCompatActivity { + ... + private MyApplication app; + private RequestQueue requestQueue; - public ImageLoader getImageLoader() { - return imageLoader; + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ... + app = (MyApplication) getApplication(); + requestQueue = Volley.newRequestQueue(this); } } ``` -Here are some examples of performing `RequestQueue` operations using the singleton class: +## RequestQueue managed by Repository +See the code sample below for a `Repository` which manages its own `RequestQueue`: ### Kotlin ``` -// Get a RequestQueue -val queue = MySingleton.getInstance(this.applicationContext).requestQueue +class UserRepository(context: Context) { + private val requestQueue: RequestQueue; -// ... + init { + requestQueue = Volley.newRequestQueue(context.getApplicationContext()); + } -// Add a request (in this example, called stringRequest) to your RequestQueue. -MySingleton.getInstance(this).addToRequestQueue(stringRequest) + fun getUsers(listener: Response.Listener>, errorListener: Response.ErrorListener) { + val request = ... + requestQueue.add(request); + } + + fun getUserById(userId: String, listener: Response.Listener, errorListener: Response.ErrorListener) { + val request = ... + requestQueue.add(request); + } + + fun stop() { + requestQueue.stop() + } +} ``` ### Java ``` -// Get a RequestQueue -RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()). - getRequestQueue(); +public class UserRepository { + private RequestQueue requestQueue; -// ... + public UserRepository(Context context) { + requestQueue = Volley.newRequestQueue(context.getApplicationContext()); + } -// Add a request (in this example, called stringRequest) to your RequestQueue. -MySingleton.getInstance(this).addToRequestQueue(stringRequest); + public void getUsers(Response.Listener> listener, Response.ErrorListener errorListener) { + Request request = ... + requestQueue.add(request); + } + + public void getUserById(String userId, Response.Listener listener, Response.ErrorListener errorListener) { + Request request = ... + requestQueue.add(request); + } + + public void stop() { + requestQueue.stop(); + } +} ``` -## Previous Lesson -[Sending a Simple Request](request-simple.md) -## Next Lesson -[Make a standard request](request-standard.md) +## Previous Lesson +[Implement a custom request](request-custom.md) diff --git a/docs/request-simple.md b/docs/request-simple.md index 21bd7dd3..5f84cab9 100644 --- a/docs/request-simple.md +++ b/docs/request-simple.md @@ -1,142 +1,154 @@ # Send a simple request -At a high level, you use Volley by creating a `RequestQueue` and passing it `Request` objects. The `RequestQueue` manages worker threads for running the network operations, reading from and writing to the cache, and parsing responses. Requests do the parsing of raw responses and Volley takes care of dispatching the parsed response back to the main thread for delivery. +This lesson describes how to send a simple request using Volley, and how to cancel a request. -This lesson describes how to send a request using the `Volley.newRequestQueue` convenience method, which sets up a RequestQueue for you. See the next lesson, [Setting Up a RequestQueue](request-queue.md), for information on how to set up a RequestQueue yourself. +At a high level, you use Volley by creating a `RequestQueue` and enqueuing `Request` objects as needed. The `RequestQueue` manages worker threads for running the network operations, reading from and writing to the cache, and parsing responses. Requests do the parsing of raw responses and Volley takes care of dispatching the parsed response back to the main thread for delivery. -This lesson also describes how to add a request to a `RequestQueue` and cancel a request. +## Add the gradle dependency +The easiest way to add Volley to your project is to add the following dependency to your app's build.gradle file: + +``` +dependencies { + ... + implementation 'com.android.volley:volley:1.2.0' +} +``` ## Add the INTERNET permission To use Volley, you must add the [android.permission.INTERNET](https://developer.android.com/reference/android/Manifest.permission#INTERNET) permission to your app's manifest. Without this, your app won't be able to connect to the network. -## Use newRequestQueue +``` + +``` -Volley provides a convenience method `Volley.newRequestQueue` that sets up a `RequestQueue` for you, using default values, and starts the queue. For example: +## Send a request ### Kotlin ``` -val textView = findViewById(R.id.text) -// ... - // Instantiate the RequestQueue. -val queue = Volley.newRequestQueue(this) -val url = "https://www.google.com" - -// Request a string response from the provided URL. -val stringRequest = StringRequest(Request.Method.GET, url, - Response.Listener { response -> - // Display the first 500 characters of the response string. - textView.text = "Response is: ${response.substring(0, 500)}" - }, - Response.ErrorListener { textView.text = "That didn't work!" }) +val requestQueue = Volley.newRequestQueue(this); + +// Request the contents of the provided URL as a raw string. +val request: Request = StringRequest( + Request.Method.GET, + "https://www.example.com", + (response: String) -> { + Log.i("Volley", "Response: " + response); + }, + (error: VolleyError) -> { + Log.e("Volley", "Error: " + error.getMessage(), error); + } +); // Add the request to the RequestQueue. -queue.add(stringRequest) +requestQueue.add(request); ``` ### Java ``` -final TextView textView = (TextView) findViewById(R.id.text); -// ... - // Instantiate the RequestQueue. -RequestQueue queue = Volley.newRequestQueue(this); -String url ="https://www.google.com"; - -// Request a string response from the provided URL. -StringRequest stringRequest = new StringRequest(Request.Method.GET, url, - new Response.Listener() { - @Override - public void onResponse(String response) { - // Display the first 500 characters of the response string. - textView.setText("Response is: "+ response.substring(0,500)); - } -}, new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - textView.setText("That didn't work!"); - } -}); +RequestQueue requestQueue = Volley.newRequestQueue(this); + +// Request the contents of the provided URL as a raw string. +Request request = new StringRequest( + Request.Method.GET, + "https://www.example.com", + (String response) -> { + Log.i("Volley", "Response: " + response); + }, + (VolleyError error) -> { + Log.e("Volley", "Error: " + error.getMessage(), error); + } +); // Add the request to the RequestQueue. -queue.add(stringRequest); +requestQueue.add(request); ``` -Volley always delivers parsed responses on the main thread. Running on the main thread is convenient for populating UI controls with received data, as you can freely modify UI controls directly from your response handler, but it's especially critical to many of the important semantics provided by the library, particularly related to canceling requests. - -See [Setting Up a RequestQueue](request-queue.md) for a description of how to set up a RequestQueue yourself, instead of using the Volley.newRequestQueue convenience method. - -## Send a request - -To send a request, you simply construct one and add it to the `RequestQueue` with `add()`, as shown above. Once you add the request it moves through the pipeline, gets serviced, and has its raw response parsed and delivered. +To send a request, you simply construct a `Request` and enqueue it to the `RequestQueue` with `add()`. Once you enqueue the request it moves through the pipeline, gets serviced, has its raw response parsed, and is delivered back to your callback. -When you call `add()`, Volley runs one cache processing thread and a pool of network dispatch threads. When you add a request to the queue, it is picked up by the cache thread and triaged: if the request can be serviced from cache, the cached response is parsed on the cache thread and the parsed response is delivered on the main thread. If the request cannot be serviced from cache, it is placed on the network queue. The first available network thread takes the request from the queue, performs the HTTP transaction, parses the response on the worker thread, writes the response to cache, and posts the parsed response back to the main thread for delivery. - -Note that expensive operations like blocking I/O and parsing/decoding are done on worker threads. You can add a request from any thread, but responses are always delivered on the main thread. - -Figure 1 illustrates the life of a request: +Volley always delivers parsed responses on the main thread. Running on the main thread is convenient for populating UI controls with received data, as you can freely modify UI controls directly from your response handler, but it's especially critical to many of the important semantics provided by the library, particularly related to canceling requests. -![Figure 1. Life of a request](https://developer.android.com/images/training/volley-request.png?authuser=1) +Note that expensive operations like blocking I/O and parsing/decoding are done on worker threads. You can enqueue a request from any thread, but responses are always delivered on the main thread. ## Cancel a request -To cancel a request, call `cancel()` on your `Request` object. Once cancelled, Volley guarantees that your response handler will never be called. What this means in practice is that you can cancel all of your pending requests in your activity's `onStop()` method and you don't have to litter your response handlers with checks for `getActivity() == null`, whether `onSaveInstanceState()` has been called already, or other defensive boilerplate. - -To take advantage of this behavior, you would typically have to track all in-flight requests in order to be able to cancel them at the appropriate time. There is an easier way: you can associate a tag object with each request. You can then use this tag to provide a scope of requests to cancel. For example, you can tag all of your requests with the `Activity` they are being made on behalf of, and call `requestQueue.cancelAll(this)` from `onStop()`. Similarly, you could tag all thumbnail image requests in a `ViewPager` tab with their respective tabs and cancel on swipe to make sure that the new tab isn't being held up by requests from another one. - -Here is an example that uses a string value for the tag: - -1. Define your tag and add it to your requests. +To cancel a request, call `cancel()` on your `Request` object. Once cancelled, Volley guarantees that your response handler will not be called. This allows you to cancel a pending request when your activity/service/worker is stopped. ### Kotlin ``` -val TAG = "MyTag" -val stringRequest: StringRequest // Assume this exists. -val requestQueue: RequestQueue? // Assume this exists. +// Add a field to hold a reference to the pending request. +private val request: Request = null -// Set the tag on the request. -stringRequest.tag = TAG +// Create and enqueue a request. +request = ... +requestQueue.add(request) -// Add the request to the RequestQueue. -requestQueue?.add(stringRequest) +// Cancel the request when the activity is stopped. +override fun onStop() { + super.onStop() + request?.cancel() +} ``` - ### Java ``` -public static final String TAG = "MyTag"; -StringRequest stringRequest; // Assume this exists. -RequestQueue requestQueue; // Assume this exists. +// Add a field to hold a reference to the pending request. +private Request request = null; -// Set the tag on the request. -stringRequest.setTag(TAG); +// Create and enqueue a request. +request = ... +requestQueue.add(request); -// Add the request to the RequestQueue. -requestQueue.add(stringRequest); +// Cancel the request when the activity is stopped. +@Override +protected void onStop() { + super.onStop(); + if (request != null) { + request.cancel(); + } +} ``` -2. In your activity's onStop() method, cancel all requests that have this tag. +However, to use this approach, you would need to track all in-flight requests in order to cancel all of them at the appropriate time. Which can easily lead to memory leaks and race conditions. But there is an easier way: you can associate a tag with each request. You can then use this tag to provide a scope of requests to cancel. + +Here is an example that uses a constant string value for the tag: ### Kotlin ``` -protected fun onStop() { - super.onStop() - requestQueue?.cancelAll(TAG) -} -``` +// Define your tag. +val VOLLEY_TAG = "MainActivity" +// Create and enqueue a request. +val request = ... +request.tag = VOLLEY_TAG +requestQueue.add(request) + +// Cancel all requests that have this tag +requestQueue?.cancelAll(VOLLEY_TAG) +``` ### Java ``` -@Override -protected void onStop () { - super.onStop(); - if (requestQueue != null) { - requestQueue.cancelAll(TAG); - } +// Define your tag. +public static final String VOLLEY_TAG = "MainActivity"; + +// Create and enqueue a request. +Request request = ... +request.setTag(VOLLEY_TAG); +requestQueue.add(request); + +// Cancel all requests that have this tag +if (requestQueue != null) { + requestQueue.cancelAll(TAG); } ``` +For example, you can tag all of your requests with the activity/service/worker they are being made on behalf of, and cancel them when the component becomes stopped. Similarly, you could tag all the requests in a `ViewPager` with their respective tab ids, and cancel on swipe to make sure that the new tab isn't being held up by requests from another one. + Take care when canceling requests. If you are depending on your response handler to advance a state or kick off another process, you need to account for this. Again, the response handler will not be called. -## Next Lesson +## Setting up a RequestQueue -[Set up RequestQueue](request-queue.md) +Volley provides a convenience method `Volley.newRequestQueue` that sets up a `RequestQueue` for you, using default values, as shown above. See [Setting up a RequestQueue](request-queue.md), for information on how to set up and configure a `RequestQueue`. + +## Next Lesson +[Make a standard request](request-standard.md) diff --git a/docs/request-standard.md b/docs/request-standard.md index dc08d4a3..28d9eede 100644 --- a/docs/request-standard.md +++ b/docs/request-standard.md @@ -1,63 +1,172 @@ # Make a standard request -This lesson describes how to use the common request types that Volley supports: +This lesson describes how to use Volley's built-in request types: -- `StringRequest`. Specify a URL and receive a raw string in response. See [Setting Up a Request Queue](request-queue.md) for an example. -- `JsonObjectRequest` and `JsonArrayRequest` (both subclasses of `JsonRequest`). Specify a URL and get a JSON object or array (respectively) in response. +- `StringRequest` - Parse the response as a raw string. +- `JsonObjectRequest` - Parse the response as an [org.json.JSONObject](https://developer.android.com/reference/org/json/JSONObject) +- `JsonArrayRequest` - Parse the response as an [org.json.JSONArray](https://developer.android.com/reference/org/json/JSONArray) -If your expected response is one of these types, you probably don't have to implement a custom request. This lesson describes how to use these standard request types. For information on how to implement your own custom request, see [Implementing a Custom Request](request-custom.md). +Extending volley with more request types will be covered in the next lesson [Implementing a Custom Request](request-custom.md) -## Request JSON -Volley provides the following classes for JSON requests: -- `JsonArrayRequest` — A request for retrieving a [JSONArray](https://developer.android.com/reference/org/json/JSONArray) response body at a given URL. -- `JsonObjectRequest` — A request for retrieving a [JSONObject](https://developer.android.com/reference/org/json/JSONObject) response body at a given URL, allowing for an optional [JSONObject](https://developer.android.com/reference/org/json/JSONObject) to be passed in as part of the request body. - -Both classes are based on the common base class `JsonRequest`. You use them following the same basic pattern you use for other types of requests. For example, this snippet fetches a JSON feed and displays it as text in the UI: +## StringRequest +`StringRequest` allows you to specify a URL and retrieve the contents as a raw string. This is suitable for simple testing, but moves the parsing of the response onto the main/ui thread, so is generally not optimal. ### Kotlin ``` -val url = "http://my-json-feed" +val request = StringRequest( + Request.Method.GET, + "https://www.example.com", + (response: String) -> { + Log.i(LOG_TAG, "Response: " + response); + }, + (error: VolleyError) -> { + Log.e(LOG_TAG, "Error: " + error.getMessage(), error); + } +); +requestQueue.add(request); +``` + +### Java +``` +Request request = new StringRequest( + Request.Method.GET, + "https://www.example.com", + (String response) -> { + Log.i(LOG_TAG, "Response: " + response); + }, + (VolleyError error) -> { + Log.e(LOG_TAG, "Error: " + error.getMessage(), error); + } +); +requestQueue.add(request); +``` + +### Posting data with StringRequest + +*`StringRequest` does not currently provide a mechanism to POST data to the URL.* -val jsonObjectRequest = JsonObjectRequest(Request.Method.GET, url, null, - Response.Listener { response -> - textView.text = "Response: %s".format(response.toString()) - }, - Response.ErrorListener { error -> - // TODO: Handle error - } -) -// Access the RequestQueue through your singleton class. -MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest) +## JsonObjectRequest +`JsonObjectRequest` allows you to specify a URL and parse the contents as an [org.json.JSONObject](https://developer.android.com/reference/org/json/JSONObject). This will send the request and parse the response on the background thread, which is preferable over `StringRequest`. + +### Kotlin ``` +val request = JsonObjectRequest( + Request.Method.GET, + "http://time.jsontest.com", + null, // indicates no data will posted as request body + (response: JSONObject) -> { + Log.i(LOG_TAG, "Response: " + response); + }, + (error: VolleyError) -> { + Log.e(LOG_TAG, "Error: " + error.getMessage(), error); + } +); +requestQueue.add(request); +``` + ### Java ``` -String url = "http://my-json-feed"; +Request request = new JsonObjectRequest( + Request.Method.GET, + "http://time.jsontest.com", + null, // indicates no data will posted as request body + (JSONObject response) -> { + Log.i(LOG_TAG, "Response: " + response); + }, + (VolleyError error) -> { + Log.e(LOG_TAG, "Error: " + error.getMessage(), error); + } +); +requestQueue.add(request); +``` + +### Posting data with JsonObjectRequest +`JsonObjectRequest` also allows to send an [org.json.JSONObject](https://developer.android.com/reference/org/json/JSONObject) as the request body. + +### Kotlin +``` +val requestData = JSONObject() +requestData.put("id", 123) +requestData.put("name", "example") + +val request = JsonObjectRequest( + Request.Method.POST, + "https://reqres.in/api/users", + requestData, + (response: JSONObject) -> { + Log.i(LOG_TAG, "Response: " + response); + }, + (error: VolleyError) -> { + Log.e(LOG_TAG, "Error: " + error.getMessage(), error); + } +); +requestQueue.add(request); +``` -JsonObjectRequest jsonObjectRequest = new JsonObjectRequest - (Request.Method.GET, url, null, new Response.Listener() { +### Java +``` +JSONObject requestData = new JSONObject(); +requestData.put("id", 123); +requestData.put("name", "example"); + +Request request = new JsonObjectRequest( + Request.Method.POST, + "https://reqres.in/api/users", + requestData, + (JSONObject response) -> { + Log.i(LOG_TAG, "Response: " + response); + }, + (VolleyError error) -> { + Log.e(LOG_TAG, "Error: " + error.getMessage(), error); + } +); +requestQueue.add(request); +``` - @Override - public void onResponse(JSONObject response) { - textView.setText("Response: " + response.toString()); - } -}, new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - // TODO: Handle error +## JsonArrayRequest +`JsonArrayRequest` allows you to specify a URL and parse the contents as an [org.json.JSONArray](https://developer.android.com/reference/org/json/JSONArray). This will send the request and parse the response on the background thread, which is preferable over `StringRequest`. - } -}); +### Kotlin +``` +val request = JsonArrayRequest( + "https://jsonplaceholder.typicode.com/users", + (response: JSONArray) -> { + Log.i(LOG_TAG, "Response: " + response); + }, + (error: VolleyError) -> { + Log.e(LOG_TAG, "Error: " + error.getMessage(), error); + } +); +requestQueue.add(request); +``` -// Access the RequestQueue through your singleton class. -MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest); +### Java ``` +Request request = new JsonArrayRequest( + "https://jsonplaceholder.typicode.com/users", + (JSONArray response) -> { + Log.i(LOG_TAG, "Response: " + response); + }, + (VolleyError error) -> { + Log.e(LOG_TAG, "Error: " + error.getMessage(), error); + } +); +requestQueue.add(request); +``` + +### Posting data with JsonArrayRequest +*`JsonArrayRequest` does not currently allow you to POST a [org.json.JSONObject](https://developer.android.com/reference/org/json/JSONObject) to the URL. But you can create a custom `Request` type to implement this.* + + + +### Using other JSON libraries +See [Implementing a Custom Request](request-custom.md) for how to use other JSON libraries with Volley. -For an example of implementing a custom JSON request based on [Gson](https://github.com/google/gson), see the next lesson, [Implement a custom request](request-custom.md). ## Previous Lesson -[Set up RequestQueue](request-queue.md) +[Send a simple request](request-simple.md) ## Next Lesson [Implement a custom request](request-custom.md) \ No newline at end of file From 0f5b93b349f83f3f0ee3abfb0232816b3e628a67 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Wed, 7 Apr 2021 09:18:18 -0500 Subject: [PATCH 3/6] fixing some error in the custom request example code --- docs/request-custom.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/request-custom.md b/docs/request-custom.md index ea8e6a05..fd739e01 100644 --- a/docs/request-custom.md +++ b/docs/request-custom.md @@ -89,16 +89,17 @@ class GsonRequest( errorListener: Response.ErrorListener? ) : Request(method, url, errorListener) { + private val LOG_TAG = GsonRequest.class.getSimpleName() private val gson = Gson() - override fun getHeaders(): MutableMap = headers ?: super.getHeaders() + override fun getHeaders(): MutableMap = headers ?: Collections.emptyMap() override fun getBodyContentType(): String = "application/json; charset=utf-8" override fun getBody(): byte[] { try { if (requestBody != null) { - return gson.toJson(requestBody).getBytes("utf-8") + return gson.toJson(requestBody).getBytes(StandardCharsets.UTF_8) } else { return null; } @@ -126,11 +127,13 @@ class GsonRequest( ### Java ``` public class GsonRequest extends Request { + private static final String LOG_TAG = GsonRequest.class.getSimpleName(); + private final Gson gson = new Gson(); private final Class clazz; private final Map headers; private final Object requestBody; - private final Listener listener; + private final Response.Listener listener; /** * Make a GET request and return a parsed object from JSON. @@ -150,18 +153,19 @@ public class GsonRequest extends Request { Class clazz, @Nullable Map headers, @Nullable Object requestBody, - @Nullable Listener listener, - @Nullable ErrorListener errorListener + @Nullable Response.Listener listener, + @Nullable Response.ErrorListener errorListener ) { super(method, url, errorListener); this.clazz = clazz; this.headers = headers; + this.requestBody = requestBody; this.listener = listener; } @Override public Map getHeaders() { - return headers != null ? headers : super.getHeaders(); + return headers != null ? headers : Collections.emptyMap(); } @Override @@ -173,7 +177,7 @@ public class GsonRequest extends Request { public byte[] getBody() { try { if (requestBody != null) { - return gson.toJson(requestBody).getBytes("utf-8"); + return gson.toJson(requestBody).getBytes(StandardCharsets.UTF_8); } else { return null; } From 5d899bb4b7c5436cb0cc5a77acb9d057ce9cfbf7 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Wed, 7 Apr 2021 09:43:23 -0500 Subject: [PATCH 4/6] adding a lesson about using workmanager --- docs/readme.md | 4 +++ docs/request-queue.md | 3 ++ docs/request-workmanager.md | 72 +++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 docs/request-workmanager.md diff --git a/docs/readme.md b/docs/readme.md index ea520f71..6f789feb 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -42,3 +42,7 @@ dependencies { - [Setting up a RequestQueue](request-queue.md) Learn how to customize your `RequestQueue`. + +- [Using Volley with WorkManager](request-workmanager.md) + + How to implement a `ListenableWorker` which makes asynchronous requests using volley. diff --git a/docs/request-queue.md b/docs/request-queue.md index 860f0510..659d6a57 100644 --- a/docs/request-queue.md +++ b/docs/request-queue.md @@ -215,3 +215,6 @@ public class UserRepository { ## Previous Lesson [Implement a custom request](request-custom.md) + +## Next Lesson +[Using Volley with WorkManager](request-workmanager.md) diff --git a/docs/request-workmanager.md b/docs/request-workmanager.md new file mode 100644 index 00000000..22334a23 --- /dev/null +++ b/docs/request-workmanager.md @@ -0,0 +1,72 @@ +# Using Volley with WorkManager + +Using *WorkManager* and *Volley* together requires some special considerations: +- Volley executes requests asynchronously on a background thread. +- A [Workers](https://developer.android.com/reference/androidx/work/Worker) is required to perform its synchronously on the provided background thread. +- When you need to call asynchronous APIs, you should use [ListenableWorker](https://developer.android.com/reference/androidx/work/ListenableWorker) instead. + +As such, this lesson will cover how to create a `ListenableWorker` that executes an asynchronous request with volley. + +## Required Dependencies +The code below requires the following additional dependencies: + +``` +implementation 'androidx.work:work-runtime:2.5.0' +implementation 'androidx.concurrent:concurrent-futures:1.1.0' +``` + +## +An example `ListenableWorker` which retrieves a single user object from a REST API is shown below. + +### Java + +``` +public class GetUserWorker extends ListenableWorker { + private static final String LOG_TAG = MyWorker.class.getSimpleName(); + + private MyApp app; + private RequestQueue requestQueue; + + public GetUserWorker(@NonNull Context context, @NonNull WorkerParameters params) { + super(context, params); + app = (MyApp) context; + requestQueue = app.getRequestQueue(); + } + + @NonNull + @Override + public ListenableFuture startWork() { + return CallbackToFutureAdapter.getFuture(resolver -> { + Request request = new JsonObjectRequest( + Request.Method.GET, + "https://reqres.in/api/users/2", + null, + (JSONObject response) -> { + // process response ... + resolver.set(Result.success()); + + }, + (VolleyError error) -> { + // handle error ... + resolver.set(Result.retry()); + } + ); + + requestQueue.add(request); + return request; + }); + } +} +``` + +## Further Reading +- [Schedule tasks with WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager) +- [WorkManager basics](https://medium.com/androiddevelopers/workmanager-basics-beba51e94048) +- [Worker](https://developer.android.com/reference/androidx/work/Worker) +- [ListenableWorker](https://developer.android.com/reference/androidx/work/ListenableWorker) +- [Threading in ListenableWorker](https://developer.android.com/topic/libraries/architecture/workmanager/advanced/listenableworker) +- [androidx.concurrent](https://developer.android.com/jetpack/androidx/releases/concurrent) +- [CallbackToFutureAdapter](https://developer.android.com/reference/androidx/concurrent/futures/CallbackToFutureAdapter) + +## Previous Lesson +[Setting up a RequestQueue](request-queue.md) From 0776c7c77ad70173e3b599d8fab0d8ddcf7fa18b Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Wed, 7 Apr 2021 11:05:28 -0500 Subject: [PATCH 5/6] Adding another article related to singletons --- docs/request-queue.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/request-queue.md b/docs/request-queue.md index 659d6a57..05ca5a10 100644 --- a/docs/request-queue.md +++ b/docs/request-queue.md @@ -45,6 +45,7 @@ There are three common patterns for managing the instances of `RequestQueue`: - The `Application` manages the shared `RequestQueue`. This ensures that there is only one `RequestQueue` instance and prevents multiple `Requests` from being in-flight at the same time. - If you are using the [Android Architecture Components](https://developer.android.com/topic/libraries/architecture), then the [Repositories](https://developer.android.com/jetpack/guide#recommended-app-arch)/[DataSources](https://developer.android.com/topic/libraries/architecture/paging/data)/[ViewModels](https://developer.android.com/topic/libraries/architecture/viewmodel) can manage the `RequestQueue` instances. - Using Singletons is **strongly discouraged.** + - [Singleton Considered Stupid](https://sites.google.com/site/steveyegge2/singleton-considered-stupid) - [So Singletons are bad, then what?](https://softwareengineering.stackexchange.com/questions/40373/so-singletons-are-bad-then-what) From 2b412ff3c4bcd41909925d32ea88bb7e23bf3a49 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Fri, 2 Jul 2021 14:19:58 -0500 Subject: [PATCH 6/6] minor text updates --- docs/readme.md | 2 -- docs/request-workmanager.md | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 6f789feb..bcbe205d 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -3,8 +3,6 @@ Volley is an HTTP library that makes networking for Android apps easier and most importantly, faster. Volley offers the following benefits: -- Automatic scheduling of network requests. -- Multiple concurrent network connections. - Transparent disk and memory response caching with standard [HTTP Cache Coherence.](https://en.wikipedia.org/wiki/Cache_coherence) - Support for request prioritization. - Cancellation request API. You can cancel a single request, or you can set blocks or scopes of requests to cancel. diff --git a/docs/request-workmanager.md b/docs/request-workmanager.md index 22334a23..57764e54 100644 --- a/docs/request-workmanager.md +++ b/docs/request-workmanager.md @@ -2,7 +2,7 @@ Using *WorkManager* and *Volley* together requires some special considerations: - Volley executes requests asynchronously on a background thread. -- A [Workers](https://developer.android.com/reference/androidx/work/Worker) is required to perform its synchronously on the provided background thread. +- A [Worker](https://developer.android.com/reference/androidx/work/Worker) is required to perform its synchronously on the provided background thread. - When you need to call asynchronous APIs, you should use [ListenableWorker](https://developer.android.com/reference/androidx/work/ListenableWorker) instead. As such, this lesson will cover how to create a `ListenableWorker` that executes an asynchronous request with volley.