Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatic 'loading' indicator when calling an async function

Tags:

java

gwt

I am looking for a way to automate showing and hiding a 'loading' message when calling an async service, so instead of doing this:

showLoadingWidget();

service.getShapes(dbName, new AsyncCallback() {
  public void onSuccess(Shape[] result) {
    hideLoadingWidget();
    // more here...
  }

  public void onFailure(Throwable caught) {
    hideLoadingWidget();
  //more here
  }
});

I'd like to do just this, but still show and hide the message at completion.

// this should be gone: showLoadingWidget();
service.getShapes(dbName, new AsyncCallback() {
    public void onSuccess(Shape[] result) {
        // this should be gone: hideLoadingWidget();
        // more here...
    }
    public void onFailure(Throwable caught) {
        //this should be gone:  hideLoadingWidget();
        //more here
    }
});

In short I would like to change the behavior of the async calls. Thank you for all the possible suggestions.

Daniel

like image 548
supercobra Avatar asked Aug 20 '09 23:08

supercobra


2 Answers

The following AsyncCall is what I'm currently using (inspired by David Tinker's solution). Instead of retrying, this expects some RPC calls to take long time to return and displays a loading indicator if the call haven't returned before a specified timeout.

This AsyncCall also keeps track of the number of RPC calls currently in progress, and only hides the loading indicator if all RPC calls have returned. With David's solution, the loading indicator might be hidden by an earlier RPC call returning even though another is still in progress. This if course assumes the loading indicator widget is global for the app, which it is in my case.

public abstract class AsyncCall<T> {
    private static final int LOADING_TOLERANCE_MS = 100;

    private static int loadingIndicatorCount = 0;

    private Timer timer;
    private boolean incremented;
    private boolean displayFailure;

    public AsyncCall(boolean displayFailure) {
        this.displayFailure = displayFailure;

        timer = new Timer() {
            @Override
            public void run() {
                if (loadingIndicator++ == 0)
                    // show global loading widget here
                incremented = true;
            }
        };
        timer.schedule(LOADING_TOLERANCE_MS);

        call(new AsyncCallback<T>() {
            @Override
            public void onSuccess(T result) {
                timer.cancel();
                if (incremented && --loadingIndicatorCount == 0)
                    // hide global loading widget here
                callback(result);
            }

            @Override
            public void onFailure(Throwable caught) {
                timer.cancel();
                if (incremented && --loadingIndicatorCount == 0)
                    // hide global loading widget here
                if (AsyncCall.this.displayFailure)
                    // show error to user here
            }
        });

    protected abstract void call(AsyncCallback<T> cb);

    protected void callback(T result) {
        // might just be a void result or a result we 
        // wish to ignore, so do not force implementation
        // by declaring as abstract
    }
}
like image 37
joscarsson Avatar answered Oct 19 '22 01:10

joscarsson


You could wrap the call itself in an object that handles displaying the loading message, maybe retrying a few times on errors or whatever. Something like this:

public abstract class AsyncCall<T> implements AsyncCallback<T> {

    /** Call the service method using cb as the callback. */
    protected abstract void callService(AsyncCallback<T> cb);

    public void go(int retryCount) {
        showLoadingMessage();
        execute(retryCount);
    }

    private void execute(final int retriesLeft) {
        callService(new AsyncCallback<T>() {
            public void onFailure(Throwable t) {
                GWT.log(t.toString(), t);
                if (retriesLeft <= 0) {
                    hideLoadingMessage();
                    AsyncCall.this.onFailure(t);
                } else {
                    execute(retriesLeft - 1);
                }
            }
            public void onSuccess(T result) {
                hideLoadingMessage();
                AsyncCall.this.onSuccess(result);
            }
        });
    }

    public void onFailure(Throwable t) {
        // standard error handling
    }
    ...
}

To make the call do something like this:

new AsyncCall<DTO>() {
    protected void callService(AsyncCallback<DTO> cb) {
        DemoService.App.get().someService("bla", cb);
    }
    public void onSuccess(DTO result) {
        // do something with result
    }
}.go(3); // 3 retries

You could extend this with code to detect calls that are taking a long time and display a busy indicator of some kind etc.

like image 63
David Tinker Avatar answered Oct 19 '22 01:10

David Tinker