141

Imagine I'm in a Service that already has a background thread. Can I do a request using volley in that same thread, so that callbacks happen synchronously?

There are 2 reasons for this:

  • First, I do not need another thread and it would be a waste to create it.
  • Second, if I'm in a ServiceIntent, the execution of the thread will finish before the callback, and therefor I will have no response from Volley. I know I can create my own Service that has some thread with a runloop I can control, but it would be desirable having this functionality in volley.
İsmail Y.
  • 3,579
  • 5
  • 21
  • 29
LocoMike
  • 5,626
  • 5
  • 30
  • 43

8 Answers8

194

It looks like it is possible with Volley's RequestFuture class. For example, to create a synchronous JSON HTTP GET request, you can do the following:

RequestFuture<JSONObject> future = RequestFuture.newFuture();
JsonObjectRequest request = new JsonObjectRequest(URL, new JSONObject(), future, future);
requestQueue.add(request);

try {
  JSONObject response = future.get(); // this will block
} catch (InterruptedException e) {
  // exception handling
} catch (ExecutionException e) {
  // exception handling
}
Blundell
  • 75,855
  • 30
  • 208
  • 233
Matthew
  • 6,356
  • 9
  • 47
  • 59
  • Are you sure that JSONObjectRequest has this new JsonObjectRequest(URL, future, future) constructer? – tasomaniac Aug 20 '13 at 13:43
  • 5
    @tasomaniac Updated. This uses the `JsonObjectRequest(String url, JSONObject jsonRequest, Listener listener, ErrorListener errorlistener)` constructor. `RequestFuture` implements both the `Listener` and `ErrorListener` interfaces, so it can be used as the last two parameters. – Matthew Aug 20 '13 at 17:21
  • 24
    it is blocking forever ! – Mohammed Subhi Sheikh Quroush Jan 19 '14 at 23:22
  • 10
    Hint: it might be blocking forever if you call future.get() BEFORE you add the request to the request queue. – dy_ Mar 27 '14 at 02:12
  • 3
    it Will be blocked for ever because you may have a connection error read Blundell Answer – Mina Gabriel Sep 29 '14 at 14:12
  • 1
    This sucks with the interruptedexception because this exception is thrown also if the response is delivery. have a look at the code of the RequestFuture code. – Erhard Dinhobl Sep 25 '15 at 09:50
  • 4
    One should say you shouldn't do this on the main thread. That was not clear to me. Cause if the main thread is blocked by the `future.get()` then the app will stall or run into timeout for sure if set so. – r00tandy Jun 01 '16 at 05:11
  • can I upload the image with above volley request? – Maroti Aug 29 '16 at 06:30
  • How to add header with RequestFuture in both method Get and Post? – Maroti Aug 29 '16 at 07:52
  • not working for normal case.if i am using thread working fine.Please give me the correct approach . – Vinoj Vetha Oct 17 '18 at 15:24
  • 1
    Note that you need to add a parameter to the `JsonObjectRequest()` which indicates the HTTP variable. Currently the default is a `POST` request. For a `GET` request use e.g. `JsonObjectRequest(Request.Method.GET, ...)` from Volley. – Seba M Feb 24 '20 at 13:22
131

Note @Matthews answer is correct BUT if you are on another thread and you do a volley call when you have no internet, your error callback will be called on the main thread, but the thread you are on will be blocked FOREVER. (Therefore if that thread is an IntentService, you will never be able to send another message to it and your service will be basically dead).

Use the version of get() that has a timeout future.get(30, TimeUnit.SECONDS) and catch the error to exit your thread.

To match @Mathews answer:

        try {
            return future.get(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            // exception handling
        } catch (ExecutionException e) {
            // exception handling
        } catch (TimeoutException e) {
            // exception handling
        }

Below I wrapped it in a method & use a different request:

   /**
     * Runs a blocking Volley request
     *
     * @param method        get/put/post etc
     * @param url           endpoint
     * @param errorListener handles errors
     * @return the input stream result or exception: NOTE returns null once the onErrorResponse listener has been called
     */
    public InputStream runInputStreamRequest(int method, String url, Response.ErrorListener errorListener) {
        RequestFuture<InputStream> future = RequestFuture.newFuture();
        InputStreamRequest request = new InputStreamRequest(method, url, future, errorListener);
        getQueue().add(request);
        try {
            return future.get(REQUEST_TIMEOUT, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Log.e("Retrieve cards api call interrupted.", e);
            errorListener.onErrorResponse(new VolleyError(e));
        } catch (ExecutionException e) {
            Log.e("Retrieve cards api call failed.", e);
            errorListener.onErrorResponse(new VolleyError(e));
        } catch (TimeoutException e) {
            Log.e("Retrieve cards api call timed out.", e);
            errorListener.onErrorResponse(new VolleyError(e));
        }
        return null;
    }
Blundell
  • 75,855
  • 30
  • 208
  • 233
  • 1
    That's a fairly important point! Not sure why this answer hasn't gotten more upvotes. – Jedidja Oct 20 '14 at 00:16
  • 1
    Also worth noting is that if you pass the ExecutionException onto the same listener that you passed into the request you will be processing the exception twice. This exception occurs when an exception has occurred during the request which volley will pass through to the errorListener for you. – Stimsoni Mar 30 '15 at 00:12
  • @Blundell I don't understand your reply. If the listener is executed on the UI thread you have a background thread in wait and the UI thread that call notifyAll() so it's ok. A deadlock can happen if the delivery is done on the same thread you are blocked with the future get(). So your response seems without any sense. – greywolf82 May 17 '15 at 14:00
  • 1
    @greywolf82 `IntentService` is a thread pool executor of a single thread, therefore that IntentService will be blocked foreveras it sits in a loop – Blundell May 17 '15 at 14:13
  • @Blundell I don't understand. It sits until a notify will be called and it will be called from UI thread. Until you have two different thread I can't see the deadlock – greywolf82 May 17 '15 at 14:58
  • @greywolf82 it's not deadlock, it's a dead thread , `get()` blocks so if it is a background thread it blocks that thread. – Blundell May 17 '15 at 15:16
  • @Blundell I know that the background thread is blocked, it's calling a wait (see volley code). When the response is received (or error), volley delivers the response on the UI thread *AND* call notifyAll to un-block the thread blocked, so I really can't see your point. – greywolf82 May 17 '15 at 15:40
  • can possible to share InputStreamRequest() class code here? – Maroti Aug 29 '16 at 06:46
  • @Shailesh something like: https://github.com/georgiecasey/android-volley-inputstream-as-response/blob/master/src/com/android/volley/toolbox/InputStreamRequest.java – Blundell Aug 31 '16 at 21:52
9

It is probably recommended to use the Futures, but if for whatever reason you don't want to, instead of cooking your own synchronized blocking thing you should use a java.util.concurrent.CountDownLatch. So that would work like this..

//I'm running this in an instrumentation test, in real life you'd ofc obtain the context differently...
final Context context = InstrumentationRegistry.getTargetContext();
final RequestQueue queue = Volley.newRequestQueue(context);
final CountDownLatch countDownLatch = new CountDownLatch(1);
final Object[] responseHolder = new Object[1];

final StringRequest stringRequest = new StringRequest(Request.Method.GET, "http://google.com", new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        responseHolder[0] = response;
        countDownLatch.countDown();
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        responseHolder[0] = error;
        countDownLatch.countDown();
    }
});
queue.add(stringRequest);
try {
    countDownLatch.await();
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}
if (responseHolder[0] instanceof VolleyError) {
    final VolleyError volleyError = (VolleyError) responseHolder[0];
    //TODO: Handle error...
} else {
    final String response = (String) responseHolder[0];
    //TODO: Handle response...
}

Since people seemed to actually try to do this and ran into some trouble I decided I'd actually provide a "real life" working sample of this in use. Here it is https://github.com/timolehto/SynchronousVolleySample

Now even though the solution works, it has some limitations. Most importantly, you can't call it on the main UI thread. Volley does execute the requests on the background, but by default Volley uses the main Looper of the application to dispatch the responses. This causes a deadlock as the main UI thread is waiting for the response, but the Looper is waiting for onCreate to finish before processing the delivery. If you really really want to do this you could, instead of the static helper methods, instantiate your own RequestQueue passing it your own ExecutorDelivery tied to a Handler using a Looper which is tied to different thread from the main UI thread.

Timo
  • 3,335
  • 30
  • 25
  • This solution blocks my thread forever, changed Thread.sleep instead of countDownLatch and problem solved – snersesyan Jul 25 '18 at 15:24
  • If you can provide a complete sample of a code failing in this manner maybe we can figure out what the problem is. I don't see how sleeping in conjunction with countdown latches makes sense. – Timo Jul 27 '18 at 17:13
  • Alright @VinojVetha I've updated the answer a bit to clarify the situation and provided a GitHub repo which you can easily clone and try the code in action. If you have more issues please provide a fork of the sample repo demonstrating your problem as a reference. – Timo Oct 17 '18 at 18:18
  • This is a great solution for synchronous request so far. – bikram Mar 23 '20 at 18:39
3

You achieve this with kotlin Coroutines

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
private suspend fun request(context: Context, link : String) : String{
   return suspendCancellableCoroutine { continuation ->
      val queue = Volley.newRequestQueue(context)
      val stringRequest = StringRequest(Request.Method.GET, link,
         { response ->
            continuation.resumeWith(Result.success(response))
         },
          {
            continuation.cancel(Exception("Volley Error"))
         })

      queue.add(stringRequest)
   }
}

And call with

CoroutineScope(Dispatchers.IO).launch {
    val response = request(CONTEXT, "https://www.google.com")
    withContext(Dispatchers.Main) {
       Toast.makeText(CONTEXT, response,Toast.LENGTH_SHORT).show()
   }
}
murgupluoglu
  • 6,524
  • 4
  • 33
  • 43
2

As a complementary observation to both @Blundells and @Mathews answers, I'm not sure any call is delivered to anything but the main thread by Volley.

The Source

Having a look at the RequestQueue implementation it seems the RequestQueue is using a NetworkDispatcher to execute the request and a ResponseDelivery to deliver the result (the ResponseDelivery is injected into the NetworkDispatcher). The ResponseDelivery is in turn created with a Handler spawn from the main thread (somewhere around line 112 in the RequestQueue implementation).

Somewhere about line 135 in the NetworkDispatcher implementation it seems like also successful results are delivered through the same ResponseDelivery as any errors. Again; a ResponseDelivery based on a Handler spawn from the main thread.

Rationale

For the use-case where a request is to be made from an IntentService it's fair to assume that the thread of the service should block until we have a response from Volley (to guarantee a living runtime scope to handle the result in).

Suggested solutions

One approach would be to override the default way a RequestQueue is created, where an alternative constructor is used instead, injecting a ResponseDelivery which spawns from the current thread rather than the main thread. I haven't investigated the implications of this, however.

dbm
  • 10,376
  • 6
  • 44
  • 56
  • 1
    Implementing a custom ResponseDelivery implementation is complicated by the fact that the `finish()` method in the `Request` class and the `RequestQueue` class are package private, apart from using a reflection hack I'm not sure there's a way around that. What I ended up doing to prevent anything running on the main (UI) thread was setting up an alternative Looper thread (using `Looper.prepareLooper(); Looper.loop()`) and passing an `ExecutorDelivery` instance to the `RequestQueue`constructor with a handler for that looper. You have the overhead of another looper but stay off the main thread – Stephen James Hand Oct 25 '17 at 20:34
2

I want to add something to Matthew's accepted answer. While RequestFuture might seem to make a synchronous call from the thread you created it, it does not. Instead, the call is executed on a background thread.

From what I understand after going through the library, requests in the RequestQueue are dispatched in its start() method:

    public void start() {
        ....
        mCacheDispatcher = new CacheDispatcher(...);
        mCacheDispatcher.start();
        ....
           NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
           networkDispatcher.start();
        ....
    }

Now both CacheDispatcher and NetworkDispatcher classes extend thread. So effectively a new worker thread is spawned for dequeuing the request queue and the response is returned to the success and error listeners implemented internally by RequestFuture.

Although your second purpose is attained but you first purpose is not since a new thread is always spawned, no matter from which thread you execute RequestFuture.

In short, true synchronous request is not possible with default Volley library. Correct me if I am wrong.

Vignatus
  • 43
  • 8
1

I use a lock to achieve that effect now im wondering if its correct my way anyone want to comment ?

// as a field of the class where i wan't to do the synchronous `volley` call   
Object mLock = new Object();


// need to have the error and success listeners notifyin
final boolean[] finished = {false};
            Response.Listener<ArrayList<Integer>> responseListener = new Response.Listener<ArrayList<Integer>>() {
                @Override
                public void onResponse(ArrayList<Integer> response) {
                    synchronized (mLock) {
                        System.out.println();
                        finished[0] = true;
                        mLock.notify();

                    }


                }
            };

            Response.ErrorListener errorListener = new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    synchronized (mLock) {
                        System.out.println();
                        finished[0] = true;
                        System.out.println();
                        mLock.notify();
                    }
                }
            };

// after adding the Request to the volley queue
synchronized (mLock) {
            try {
                while(!finished[0]) {
                    mLock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
forcewill
  • 1,637
  • 2
  • 16
  • 31
  • I think you are essentially implementing what Volley already provides when you use "futures". – spaaarky21 Jan 28 '15 at 22:21
  • 1
    I would recommend having the `catch (InterruptedException e)` inside the while loop. Otherwise thread will fail to wait if interrupted for some reason – jayeffkay Apr 09 '15 at 12:45
  • @jayeffkay i am already catching the exception if an ** InterruptedException** occurs in the while loop the catch handles it. – forcewill Apr 10 '15 at 13:34
0

You can do sync request with volley but you must call the method in different thread otherwise your running app will block, it should be like this :

public String syncCall(){

    String URL = "http://192.168.1.35:8092/rest";
    String response = new String();



    RequestQueue requestQueue = Volley.newRequestQueue(this.getContext());

    RequestFuture<JSONObject> future = RequestFuture.newFuture();
    JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, URL, new JSONObject(), future, future);
    requestQueue.add(request);

    try {
        response = future.get().toString();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (JSONException e) {
        e.printStackTrace();
    }

    return response;


}

after that you can call the method in thread :

 Thread thread = new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        
                                        String response = syncCall();
    
                                    }
                                });
                                thread.start();
Brahim SLIMANI
  • 310
  • 1
  • 8