Improving the GWT Async Callback

The core of GWT framework for async communication to the server is through the async callback interface. Its interface is rather simple, but unfortunately, as with a lot GWT development the simplicity is lost with the the amount of boiler plate code you are constantly writing. You have to write a service interface, an async service interface, service implementation, and a class (or anonymous class) that extends the AsyncCallback. Also, if you have any framework code you want to add to your async calls, that is just more boiler plate code you need to add and make sure that everyone follows/uses when writing the client side calls to the async callback.

Needless to say, in my 1.5 years of writing GWT applications, I’m sick and tired of boiler plate code. I know boiler plate code is a result of issues with the Java language, but it seems like with GWT it is even more than with other projects. If only Google was working on a Groovy to Javascript GWT Compiler. But, since I’m confined to using Java, I will look into one area to reduce boiler plate code, and that is with the asynchronous service calls.

What is the GWT Asynchronous Service Interface?

The Asynchronous Service Interface is nice and simple. GWT, as with other client side libraries, have hidden the complexity of writing your AJAX calls. It is done by extending, usually through an anonymous inner class, the AsyncCallback interface.

The interface is just this:

  1. public interface AsyncCallback<T> {
  2.   void onFailure(Throwable caught);
  3.   void onSuccess(T result);
  4. }

You then have two service interfaces the standard interface, and the async interface

Standard Service Interface

  1. @RemoteServiceRelativePath("gwt/MyService")
  2. public interface MyService {
  3.   MyData getData(String parameter);
  4. }

Async Service Interface
the return parameter is wrapped as a parameter in the AsyncCallback interface (shown above).

  1. public interface MyServiceAsync {
  2.   void getData(String parameter, AsyncCallback<MyData> callback);
  3. }

Calling the Service from GWT

  1. public class MyClass {
  2.  
  3.   @Inject
  4.   private MyServiceAsync service;
  5.  
  6.   public void someMethod() {
  7.     service.getData(new AsyncCallback<MyData>() {
  8.       void onSuccess(MyData data) {
  9.         //success code goes here.
  10.       }
  11.       void onFailure(Throwable throwable) {
  12.         // failure code goes here.
  13.       }
  14.     });
  15.   }
  16. }

The boiler-plate code

There is code that I end up writing around calling all of my AJAX services. I would like to add this code w/out having boiler-plate code all around my application.

  • I want to log each call including the time needed to make the call
  • I want certain exceptions to be handled in a uniform way across all of my calls
  • I want the ability to retry the asynchronous service call.

If we look at the logging, adding code an existing Async Callback is easy enough. However, when you have many anonymous classes and more complex logging; it all adds up.

  1. public class MyClass {
  2.  
  3.   @Inject
  4.   private MyServiceAsync service;
  5.  
  6.   public void someMethod() {
  7.     long start = System.currentTimeMillis();
  8.     service.getData(new AsyncCallback<MyData>() {
  9.       void onSuccess(MyData data) {
  10.         Log.debug("time : " + (System.currentTimeMillis() - start);
  11.         //success code goes here.
  12.       }
  13.       void onFailure(Throwable throwable) {
  14.         Log.debug("time (failure) : " + (System.currentTimeMillis() - start);
  15.         // failure code goes here.
  16.       }
  17.     });
  18.   }
  19. }

Now, every time I make a service call, I need to add this boilerplate code.

The GwtCallbackWrapper

The solution is to wrap the AsyncCallback in another class, and use that class to make your async calls. Extending the AsyncCallback interface doesn’t work, since you cannot wrap the method calls and you end up providing alternate methods, but it is an interface (everythings public) so extending it just makes things more confusing. Instead, create a wrapper class that has the same methods, but creates a simple AsyncCallback that ends up calling the methods on the wrapped class. Then add a new onCall(AsyncCallback callback) method that is used to start the invocation. Then you can swap out this new class for the AsyncCallback class and your anonymous inner classes are virtually unchanged. And by adding in the onCall method, it makes retries easy.

Here is the AsyncCallback replacing that wraps that actual AsyncCallback. Since this is an abstract class, you can limit the scope of onSuccess(), onFailure(), and the newly added method onCall() to be protected. When used as an anonymous inner class, the onCall() method will have access to local variables (as long as they are final) as well as any class variables.

  1. import com.google.gwt.user.client.rpc.AsyncCallback;
  2.  
  3. public abstract class GwtCallbackWrapper<T> {
  4.   private AsyncCallback<T> asyncCallback = new AsyncCallback<T>() {
  5.     public void onSuccess(T result) {
  6.       GwtCallbackWrapper.this.onSuccess(result);
  7.     }
  8.     public void onFailure(Throwable t) {
  9.       GwtCallbackWrapper.this.onFailure(t);
  10.     }
  11.   };
  12.  
  13.   protected final AsyncCallback<T> getAsyncCallback() {
  14.     return asyncCallback;
  15.   }
  16.  
  17.   protected abstract void onSuccess(T t);
  18.   protected abstract void onFailure(Throwable throwable);
  19.   protected abstract void onCall(AsyncCallback<T> callback);
  20.  
  21.   public final void call() {
  22.     onCall(getAsyncCallback());
  23.   }
  24. }

Now, I can easily add in my aspect like behavior w/out really changing the API to the user (the onFailure() and onSuccess() method names and signatures remain unchanged).

  1. import com.allen_sauer.gwt.log.client.Log;
  2. import com.google.gwt.user.client.rpc.AsyncCallback;
  3.  
  4. public abstract class GwtCallbackWrapper<T> {
  5.  
  6.   private long start;
  7.  
  8.   private AsyncCallback<T> asyncCallback = new AsyncCallback<T>() {
  9.     public void onSuccess(T result) {
  10.       Log.debug("Time : " + (System.currentTimeMillis() - start));
  11.       GwtCallbackWrapper.this.onSuccess(result);
  12.     }
  13.  
  14.     public void onFailure(Throwable t) {
  15.       Log.debug("Time (failure) : " + (System.currentTimeMillis() - start));
  16.       GwtCallbackWrapper.this.onFailure(t);
  17.     }
  18.   };
  19.  
  20.   protected final AsyncCallback<T> getAsyncCallback() {
  21.     return asyncCallback;
  22.   }
  23.  
  24.   protected abstract void onSuccess(T t);
  25.  
  26.   protected abstract void onFailure(Throwable throwable);
  27.  
  28.   protected abstract void onCall(AsyncCallback<T> callback);
  29.  
  30.   public final void call() {
  31.     start = System.currentTimeMillis();
  32.     onCall(getAsyncCallback());
  33.   }
  34. }

When I need to make an asynchronous call, I just use my wrapper class, never using the AsyncCallback directly.

  1. public class MyClass {
  2.  
  3.   @Inject
  4.   private MyServiceAsync service;
  5.  
  6.   public void someMethod(final String parameter) {
  7.     new GwtCallbackWrapper<MyData>() {
  8.       public void onCall(AsyncCallback<MyData> callback) {
  9.         service.call(parameter, callback);
  10.       }
  11.       void onSuccess(MyData data) {
  12.         //success code goes here.
  13.       }
  14.       void onFailure(Throwable throwable) {
  15.         // failure code goes here.
  16.       }
  17.     }).call();
  18.   }
  19. }

Don’t forget you will need to make any parameter or local variable of someMethod() to be final.

Now it is easy to add in even more “aspects” to the asynchronous calls. Lets say if you get 500 error back, you want to give the user a retry dialog.

GwtCallbackWrapper

  1. import com.allen_sauer.gwt.log.client.Log;
  2. import com.google.gwt.user.client.rpc.AsyncCallback;
  3. import com.google.gwt.user.client.rpc.StatusCodeException;
  4.  
  5. public abstract class GwtCallbackWrapper<T> {
  6.  
  7.   private long start;
  8.  
  9.   private AsyncCallback<T> asyncCallback = new AsyncCallback<T>() {
  10.     public void onSuccess(T result) {
  11.       Log.debug("Time : " + (System.currentTimeMillis() - start));
  12.       GwtCallbackWrapper.this.onSuccess(result);
  13.     }
  14.  
  15.     public void onFailure(Throwable t) {
  16.       Log.debug("Time (failure) : " + (System.currentTimeMillis() - start));
  17.       if (t instanceof StatusCodeException) {
  18.         StatusCodeException e = (StatusCodeException) t;
  19.         if (e.getStatusCode() >= 500) {
  20.           new MyDialog<T>(GwtCallbackWrapper.this).center();
  21.         } else {
  22.           GwtCallbackWrapper.this.onFailure(t);
  23.         }
  24.       } else {
  25.         GwtCallbackWrapper.this.onFailure(t);
  26.       }
  27.     }
  28.   };
  29.  
  30.   protected final AsyncCallback<T> getAsyncCallback() {
  31.     return asyncCallback;
  32.    }
  33.  
  34.   protected abstract void onSuccess(T t);
  35.  
  36.   protected abstract void onFailure(Throwable throwable);
  37.  
  38.   protected abstract void onCall(AsyncCallback<T> callback);
  39.  
  40.   public final void call() {
  41.     start = System.currentTimeMillis();
  42.     onCall(getAsyncCallback());
  43.   }
  44. }

MyDialog
It can “retry” the service call, by just calling the wrapper.call() method.

  1. public class MyDialog<T> extends DialogBox {
  2.   public MyDialog(final GwtCallbackWrapper<T> wrapper) {
  3.     setText("Server not available, retry?");
  4.     Button ok = new Button("OK");
  5.     ok.addClickHandler(new ClickHandler() {
  6.       public void onClick(ClickEvent event) {
  7.         MyDialog.this.hide();
  8.         wrapper.call();
  9.       }
  10.     });
  11.     Button quit = new Button("Quit");
  12.     quit.addClickHandler(new ClickHandler() {
  13.       public void onClick(ClickEvent event) {
  14.         MyDialog.this.hide();
  15.       }
  16.     });
  17.     HorizontalPanel panel = new HorizontalPanel();
  18.     panel.add(ok);
  19.     panel.add(quit);
  20.     setWidget(panel);
  21.   }
  22. }

Summary
By wrapping the actual GWT AsyncCallback and using the wrapping class as your base class to your anonymous classes you create for your callbacks, you have any easy way to control the common code from your GWT client code, at least when it comes to making your service calls.

One thought on “Improving the GWT Async Callback

  1. Kabal says:

    Thanks. I’ve been searching it for some time.

  2. Paul says:

    Neil, this is a great article. Working through this problem with the example is ideal. Re-jigging the way AsynCallback callbacks are used becomes pretty important with more complex applications. This helped me take a new approach.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*