diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 00000000..bcbe205d --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,46 @@ +# Volley Overview + +Volley is an HTTP library that makes networking for Android apps easier and most importantly, faster. + +Volley offers the following benefits: +- 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' +} +``` + +## Lessons + +- [Send a simple request](request-simple.md) + + 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 org.json). + +- [Implement a custom request](request-custom.md) + + Learn how to implement a custom request. + +- [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-custom.md b/docs/request-custom.md new file mode 100644 index 00000000..fd739e01 --- /dev/null +++ b/docs/request-custom.md @@ -0,0 +1,216 @@ +# Implement a custom request +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. + +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. + +## 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 that you expect. +- Implement the abstract methods `parseNetworkResponse()` and `deliverResponse()`, described in more detail below. + +*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 { + 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 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[], 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. + +## 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: User) = listener?.onResponse(response) +``` +### Java +``` +@Override +protected void deliverResponse(User response) { + if (listener != null) { + 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 +``` +class GsonRequest( + 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 LOG_TAG = GsonRequest.class.getSimpleName() + private val gson = Gson() + + 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(StandardCharsets.UTF_8) + } else { + return null; + } + } catch (ex: Exception) { + // handle error ... + } + } + + 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, 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 +``` +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 Response.Listener listener; + + /** + * Make a GET request and return a parsed object from JSON. + * + * @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( + int method, + String url, + Class clazz, + @Nullable Map headers, + @Nullable Object requestBody, + @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 : Collections.emptyMap(); + } + + @Override + public String getBodyContentType() { + return "application/json; charset=utf-8"; + } + + @Override + public byte[] getBody() { + try { + if (requestBody != null) { + return gson.toJson(requestBody).getBytes(StandardCharsets.UTF_8); + } else { + return null; + } + } catch (Exception ex) { + // handle error ... + } + } + + @Override + protected Response parseNetworkResponse(NetworkResponse response) { + try { + 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); + } + } +} +``` + +## Previous Lesson +[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 new file mode 100644 index 00000000..05ca5a10 --- /dev/null +++ b/docs/request-queue.md @@ -0,0 +1,221 @@ +# Setting up a RequestQueue + +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. + +## 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. + +`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).) + +This snippet shows you the steps involved in configuring a `RequestQueue`: + +### Kotlin +``` +// Instantiate the cache +val cache = DiskBasedCache(context.getCacheDir(), 1024 * 1024) // 1MB cap + +// Use HttpURLConnection as the HTTP client +val network = BasicNetwork(HurlStack()) + +// Instantiate the RequestQueue +val requestQueue = RequestQueue(cache, network) + +// Start the queue +requestQueue.start() +``` +### Java +``` +// Instantiate the cache +Cache cache = new DiskBasedCache(context.getCacheDir(), 1024 * 1024); // 1MB cap + +// Use HttpURLConnection as the HTTP client +Network network = new BasicNetwork(new HurlStack()); + +// Instantiate the RequestQueue +RequestQueue requestQueue = new RequestQueue(cache, network); + +// Start the queue +requestQueue.start(); +``` + +## Managing RequestQueue Instances +There are three common patterns for managing the instances of `RequestQueue`: + +- 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.** + - [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) + + +## RequestQueue managed by Activity +See the code sample below for an `Activity` which manages its own `RequestQueue`: + +### Kotlin +``` +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 MyActivity extends AppCompatActivity { + ... + private RequestQueue requestQueue; + + @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(); + } + } +} +``` + +## 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: + +### Kotlin +``` +class MyApplication: Application { + ... + private val requestQueue: RequestQueue + + override fun onCreate() { + super.onCreate() + requestQueue = Volley.newRequestQueue(this) + } + + 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() { + return requestQueue; + } +} +``` + +And below is a sample `Activity` which uses it: + +### 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; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ... + app = (MyApplication) getApplication(); + requestQueue = Volley.newRequestQueue(this); + } +} +``` + +## RequestQueue managed by Repository +See the code sample below for a `Repository` which manages its own `RequestQueue`: + +### Kotlin +``` +class UserRepository(context: Context) { + private val requestQueue: RequestQueue; + + init { + requestQueue = Volley.newRequestQueue(context.getApplicationContext()); + } + + 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 +``` +public class UserRepository { + private RequestQueue requestQueue; + + public UserRepository(Context context) { + requestQueue = Volley.newRequestQueue(context.getApplicationContext()); + } + + 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 +[Implement a custom request](request-custom.md) + +## Next Lesson +[Using Volley with WorkManager](request-workmanager.md) diff --git a/docs/request-simple.md b/docs/request-simple.md new file mode 100644 index 00000000..5f84cab9 --- /dev/null +++ b/docs/request-simple.md @@ -0,0 +1,154 @@ +# Send a simple request + +This lesson describes how to send a simple request using Volley, and how to cancel a request. + +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. + +## 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. + +``` + +``` + +## Send a request + +### Kotlin +``` +// Instantiate the RequestQueue. +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. +requestQueue.add(request); +``` + +### Java +``` +// Instantiate the RequestQueue. +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. +requestQueue.add(request); +``` + +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. + +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. + +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 not be called. This allows you to cancel a pending request when your activity/service/worker is stopped. + +### Kotlin +``` +// Add a field to hold a reference to the pending request. +private val request: Request = null + +// Create and enqueue a request. +request = ... +requestQueue.add(request) + +// Cancel the request when the activity is stopped. +override fun onStop() { + super.onStop() + request?.cancel() +} +``` +### Java +``` +// Add a field to hold a reference to the pending request. +private Request request = null; + +// Create and enqueue a request. +request = ... +requestQueue.add(request); + +// Cancel the request when the activity is stopped. +@Override +protected void onStop() { + super.onStop(); + if (request != null) { + request.cancel(); + } +} +``` + +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 +``` +// 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 +``` +// 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. + +## Setting up a RequestQueue + +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 new file mode 100644 index 00000000..28d9eede --- /dev/null +++ b/docs/request-standard.md @@ -0,0 +1,172 @@ +# Make a standard request +This lesson describes how to use Volley's built-in request types: + +- `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) + +Extending volley with more request types will be covered in the next lesson [Implementing a Custom Request](request-custom.md) + + +## 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 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.* + + +## 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 +``` +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); +``` + +### 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); +``` + + +## 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); +``` + +### 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. + + +## Previous Lesson +[Send a simple request](request-simple.md) + +## Next Lesson +[Implement a custom request](request-custom.md) \ No newline at end of file diff --git a/docs/request-workmanager.md b/docs/request-workmanager.md new file mode 100644 index 00000000..57764e54 --- /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 [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. + +## 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)