34

I have a Retrofit network call that id like to run every 5 seconds. My current code:

Handler h = new Handler();
int delay = 5000; //milliseconds

h.postDelayed(new Runnable() {
    public void run() {
        call.enqueue(new Callback<ApiResponse>() {
            @Override
            public void onResponse(Response<ApiResponse> response) {
                Log.d("api", "response: " + response.body().getPosition().getLatitude().toString());
            }

            @Override
            public void onFailure(Throwable t) {

            }
        });
        h.postDelayed(this, delay);
    }
}, delay);

This runs once, but then throws the following:

java.lang.IllegalStateException: Already executed. at retrofit2.OkHttpCall.enqueue(OkHttpCall.java:52) at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall.enqueue(ExecutorCallAdapterFactory.java:57) at orbyt.project.MyFragment$1.run(MyFragment.java:93)

Whats the issue here?

As a bonus: whats a better way to handle this? Ill be updating a map every update. I was thinking about trying to use Rx but not sure if this is an appropriate use-case, or how to implement it.

Ziem
  • 6,579
  • 8
  • 53
  • 86
Orbit
  • 2,985
  • 9
  • 49
  • 106
  • is it possible that your network call is taking longer than 5 seconds, so when the second iteration starts the first iteration has not finished yet? The delay doesn't start when the first iteration ended, but 5 seconds from the time you called the first iteration – TooManyEduardos Jan 29 '16 at 21:04
  • @TooManyEduardos well I know for a fact the first call is completing because Im logging the response, and am in fact getting data. – Orbit Jan 29 '16 at 21:05
  • but the log just shows you the data received, not the network connection finished. It might still be closing elements within the 5 second window. This is just an idea, but try increasing the delay to like 15 seconds and see if the issue goes away – TooManyEduardos Jan 29 '16 at 21:06
  • @TooManyEduardos changed the delay to 15 seconds, still getting the same error. – Orbit Jan 29 '16 at 21:08
  • I don't understand what is unclear, here. it tells you what does not work, and why. If in doubt, refer to the doc https://square.github.io/okhttp/2.x/okhttp/ It plainly says `Throws: IllegalStateException - when the call has already been executed.` Your call has already been executed, since that's what you want - repeat the call - and the IllegalStateException is thrown. Nothing surprising here. – njzk2 Jan 29 '16 at 22:07
  • call even says `it cannot be executed twice.` – njzk2 Jan 29 '16 at 22:07
  • @njzk2 I had searched for solutions specific to Retrofit as well as checking out the Retrofit issue tracker, but hadn't found anything suggesting why this would be an issue. I figured it couldnt be called twice, as instantiating a new Retrofit `Call` inside the `Runnable` also worked. Jakes solution below solves the issue nicely. Thanks for the links regardless, I didnt check the actual official documentation for `Call`, which would have provided the answer. – Orbit Jan 29 '16 at 22:15

1 Answers1

81

A Call can only be used once. Its documentation tells you how to use one multiple times:

Use clone() to make multiple calls with the same parameters to the same webserver; this may be used to implement polling or to retry a failed call.

So use call.clone().enqueue(..) for Asynchornous and call.clone().execute() for Synchornous respectively to ensure that you have a fresh, unexecuted Call for each request.

Santanu Sur
  • 10,997
  • 7
  • 33
  • 52
Jake Wharton
  • 75,598
  • 23
  • 223
  • 230
  • 1
    Hi @Jake Wharton, I'm calling same API multiple times using: if( call.isExecuted() ) { call.cancel(); } call.clone().enqueue(..) , I want to receive response only from last/latest call, but getting response for every call ? – Deven Dec 03 '18 at 08:00
  • @Deven that is probably only reliably doable if you limit Ok to a single request at a time `new Retrofit.Builder().callFactory(new OkHttpClient.Builder().dispatcher(new Dispatcher().setMaxRequests(1).setMaxRequestsPerHost(1)).build()).build()`; I have definitely seen multiple enqueued requests complete out of order. – swooby Feb 25 '19 at 22:19
  • I have a doubt. Can i use call.cancel() instead of that? – Jorge Alejandro Puñales May 19 '20 at 22:23