Repository: square/retrofit
Issue: [Proposal] Add isExecuted API to Call interface
Labels: Feature

What do you think about adding a

``` java
boolean isExecuted();
```

API to the Call interface?

Would  be helpful in some situations. Of course i could handle it by myself by setting executed true in the onResponse and onFailure handling, but would still be nice to have it in retrofit.

I would also be open to add it via a PR if you are interested.


Issue comments:
For what purpose? When would you not know if a call had been executed?

On Mon, Nov 9, 2015, 9:14 AM Philip Giuliani notifications@github.com
wrote:

> What do you think about adding a
> 
> boolean isExecuted();
> 
> API to the Call interface?
> 
> Would be helpful in some situations. Of course i could handle it by myself
> by setting executed true in the onResponse and onFailure handling, but
> would still be nice to have it in retrofit.
> 
> I would also be open to add it via a PR if you are interested.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/square/retrofit/issues/1267.

In my app i have the following, simple, usecase:
I am loading some unimportant data in the background which i need when the users clicks on a button. Now if the data is not loaded yet and he clicks on the button, he should get a message that it's still loading.

In my case its not possible to disable the button (Leanback has no option for this...)

@swankjesse have opinions here? Right now Retrofit's Call mirrors OkHttp's, so if we expose this, ideally OkHttp would expose it as well.

It seems to be close to `tag` object in OkHttp call. For now, OkHttp allows user to build `Request` with `tag`, and OkHttpClient could `cancel` request for those tags. So IMO it would be nice if we could have more convenient methods for `tags` like `OkHttpClient#isExecuted(Object tag)`.

That would require keeping far too much state and it provides no way of
dealing with both executed and unexecuted calls of the same tag.

On Thu, Nov 19, 2015 at 1:00 AM Nam Nguyen Hoai notifications@github.com
wrote:

> It seems to be close to tag object in OkHttp call. For now, OkHttp allows
> user to build Request with tag, and OkHttpClient could cancel request for
> those tags. So IMO it would be nice if we could have more convenient method
> for tags like OkHttpClient#isExecuted(Object tag).
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/square/retrofit/issues/1267#issuecomment-157960228.

In OkHttp: https://github.com/square/okhttp/pull/2022


Pull request review comments:

Patch that resolved the issue:
diff --git a/retrofit-mock/src/main/java/retrofit/mock/BehaviorCall.java b/retrofit-mock/src/main/java/retrofit/mock/BehaviorCall.java
index 170c46f8de..8edf72fab8 100644
--- a/retrofit-mock/src/main/java/retrofit/mock/BehaviorCall.java
+++ b/retrofit-mock/src/main/java/retrofit/mock/BehaviorCall.java
@@ -119,6 +119,10 @@ private void callFailure(final Throwable throwable) {
     });
   }
 
+  @Override public synchronized boolean isExecuted() {
+    return executed;
+  }
+
   @Override public Response<T> execute() throws IOException {
     final AtomicReference<Response<T>> responseRef = new AtomicReference<>();
     final AtomicReference<Throwable> failureRef = new AtomicReference<>();
diff --git a/retrofit-mock/src/main/java/retrofit/mock/Calls.java b/retrofit-mock/src/main/java/retrofit/mock/Calls.java
index d56c9589ac..21343074ca 100644
--- a/retrofit-mock/src/main/java/retrofit/mock/Calls.java
+++ b/retrofit-mock/src/main/java/retrofit/mock/Calls.java
@@ -36,6 +36,10 @@ public static <T> Call<T> response(final Response<T> response) {
         callback.onResponse(response);
       }
 
+      @Override public boolean isExecuted() {
+        return false;
+      }
+
       @Override public void cancel() {
       }
 
@@ -60,6 +64,10 @@ public static <T> Call<T> failure(final IOException failure) {
         callback.onFailure(failure);
       }
 
+      @Override public boolean isExecuted() {
+        return false;
+      }
+
       @Override public void cancel() {
       }
 
diff --git a/retrofit/src/main/java/retrofit/Call.java b/retrofit/src/main/java/retrofit/Call.java
index 75e3e8a060..968aba8b49 100644
--- a/retrofit/src/main/java/retrofit/Call.java
+++ b/retrofit/src/main/java/retrofit/Call.java
@@ -46,6 +46,12 @@ public interface Call<T> extends Cloneable {
    */
   void enqueue(Callback<T> callback);
 
+  /**
+   * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain
+   * #enqueue(Callback) enqueued}. It is an error to execute or enqueue a call more than once.
+   */
+  boolean isExecuted();
+
   /**
    * Cancel this call. An attempt will be made to cancel in-flight calls, and if the call has not
    * yet been executed it never will be.
diff --git a/retrofit/src/main/java/retrofit/ExecutorCallAdapterFactory.java b/retrofit/src/main/java/retrofit/ExecutorCallAdapterFactory.java
index 2862063a0e..15ed08770c 100644
--- a/retrofit/src/main/java/retrofit/ExecutorCallAdapterFactory.java
+++ b/retrofit/src/main/java/retrofit/ExecutorCallAdapterFactory.java
@@ -78,6 +78,10 @@ static final class ExecutorCallbackCall<T> implements Call<T> {
       });
     }
 
+    @Override public boolean isExecuted() {
+      return delegate.isExecuted();
+    }
+
     @Override public Response<T> execute() throws IOException {
       return delegate.execute();
     }
diff --git a/retrofit/src/main/java/retrofit/OkHttpCall.java b/retrofit/src/main/java/retrofit/OkHttpCall.java
index 10483380ae..70cef83158 100644
--- a/retrofit/src/main/java/retrofit/OkHttpCall.java
+++ b/retrofit/src/main/java/retrofit/OkHttpCall.java
@@ -102,6 +102,10 @@ private void callSuccess(Response<T> response) {
     });
   }
 
+  @Override public synchronized boolean isExecuted() {
+    return executed;
+  }
+
   public Response<T> execute() throws IOException {
     synchronized (this) {
       if (executed) throw new IllegalStateException("Already executed");


Test patch:
diff --git a/retrofit/src/test/java/retrofit/CallTest.java b/retrofit/src/test/java/retrofit/CallTest.java
index 09966614e3..af3b1a08fe 100644
--- a/retrofit/src/test/java/retrofit/CallTest.java
+++ b/retrofit/src/test/java/retrofit/CallTest.java
@@ -515,6 +515,41 @@ public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] an
     assertThat(rawBody.contentType().toString()).isEqualTo("text/stringy");
   }
 
+  @Test public void reportsExecutedSync() throws IOException {
+    Retrofit retrofit = new Retrofit.Builder()
+        .baseUrl(server.url("/"))
+        .addConverterFactory(new ToStringConverterFactory())
+        .build();
+    Service example = retrofit.create(Service.class);
+
+    server.enqueue(new MockResponse().setBody("Hi"));
+
+    Call<String> call = example.getString();
+    assertThat(call.isExecuted()).isFalse();
+
+    call.execute();
+    assertThat(call.isExecuted()).isTrue();
+  }
+
+  @Test public void reportsExecutedAsync() throws InterruptedException {
+    Retrofit retrofit = new Retrofit.Builder()
+        .baseUrl(server.url("/"))
+        .addConverterFactory(new ToStringConverterFactory())
+        .build();
+    Service example = retrofit.create(Service.class);
+
+    server.enqueue(new MockResponse().setBody("Hi"));
+
+    Call<String> call = example.getString();
+    assertThat(call.isExecuted()).isFalse();
+
+    call.enqueue(new Callback<String>() {
+      @Override public void onResponse(Response<String> response) {}
+      @Override public void onFailure(Throwable t) {}
+    });
+    assertThat(call.isExecuted()).isTrue();
+  }
+
   @Test public void cancelBeforeExecute() {
     Retrofit retrofit = new Retrofit.Builder()
         .baseUrl(server.url("/"))
diff --git a/retrofit/src/test/java/retrofit/ExecutorCallAdapterFactoryTest.java b/retrofit/src/test/java/retrofit/ExecutorCallAdapterFactoryTest.java
index eab2de7bfc..474b882dba 100644
--- a/retrofit/src/test/java/retrofit/ExecutorCallAdapterFactoryTest.java
+++ b/retrofit/src/test/java/retrofit/ExecutorCallAdapterFactoryTest.java
@@ -151,6 +151,10 @@ static class EmptyCall implements Call<String> {
       throw new UnsupportedOperationException();
     }
 
+    @Override public boolean isExecuted() {
+      return false;
+    }
+
     @Override public Response<String> execute() throws IOException {
       throw new UnsupportedOperationException();
     }