GWT
RPC
loading screen
centralization
web development

how to centralize loading screen for GWT RPC?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In GWT RPC applications, loading indicators often end up duplicated in every callback: show spinner before the call, hide spinner in success, hide spinner again in failure. A cleaner approach is to centralize that behavior in one reusable callback or service wrapper so every RPC call participates in the same loading policy automatically.

The Core Idea: Count Active RPC Calls

A centralized loading screen should not blindly hide itself after the first callback finishes, because several RPC requests may be running at once. The safest pattern is:

  1. increment a shared counter when a request starts
  2. show the loading overlay if the counter becomes non-zero
  3. decrement when a request finishes or fails
  4. hide the overlay only when the counter returns to zero

That avoids flicker and prevents one fast RPC from hiding the spinner while another slower request is still running.

A Simple Loading Manager

java
1public class LoadingManager {
2    private static int activeRequests = 0;
3
4    public static void requestStarted() {
5        activeRequests++;
6        show();
7    }
8
9    public static void requestFinished() {
10        if (activeRequests > 0) {
11            activeRequests--;
12        }
13        if (activeRequests == 0) {
14            hide();
15        }
16    }
17
18    private static void show() {
19        // display popup, glass panel, or spinner widget
20    }
21
22    private static void hide() {
23        // hide popup, glass panel, or spinner widget
24    }
25}

This class does not care which RPC method is running. It only tracks whether the application currently has unfinished requests.

Wrap AsyncCallback

The next step is to stop repeating loading logic in every RPC call. A custom callback base class works well for that.

java
1import com.google.gwt.user.client.rpc.AsyncCallback;
2
3public abstract class LoadingCallback<T> implements AsyncCallback<T> {
4
5    public LoadingCallback() {
6        LoadingManager.requestStarted();
7    }
8
9    @Override
10    public final void onFailure(Throwable caught) {
11        LoadingManager.requestFinished();
12        handleFailure(caught);
13    }
14
15    @Override
16    public final void onSuccess(T result) {
17        LoadingManager.requestFinished();
18        handleSuccess(result);
19    }
20
21    protected abstract void handleSuccess(T result);
22
23    protected void handleFailure(Throwable caught) {
24        // default error behavior
25    }
26}

Now client code becomes much cleaner:

java
1myService.loadUsers(new LoadingCallback<List<User>>() {
2    @Override
3    protected void handleSuccess(List<User> result) {
4        view.showUsers(result);
5    }
6
7    @Override
8    protected void handleFailure(Throwable caught) {
9        view.showError(caught.getMessage());
10    }
11});

The loading screen is now centralized without losing the ability to customize success or failure handling per request.

Centralize Even Further With a Service Helper

If you want to ensure every call uses the pattern, wrap the invocation itself:

java
1public class RpcExecutor {
2    public static <T> AsyncCallback<T> wrap(AsyncCallback<T> callback) {
3        LoadingManager.requestStarted();
4
5        return new AsyncCallback<T>() {
6            @Override
7            public void onFailure(Throwable caught) {
8                LoadingManager.requestFinished();
9                callback.onFailure(caught);
10            }
11
12            @Override
13            public void onSuccess(T result) {
14                LoadingManager.requestFinished();
15                callback.onSuccess(result);
16            }
17        };
18    }
19}

Usage:

java
1myService.loadUsers(RpcExecutor.wrap(new AsyncCallback<List<User>>() {
2    @Override
3    public void onFailure(Throwable caught) {
4        view.showError(caught.getMessage());
5    }
6
7    @Override
8    public void onSuccess(List<User> result) {
9        view.showUsers(result);
10    }
11}));

This keeps the loading logic completely out of individual presenters and widgets.

Common Pitfalls

The most common mistake is using a simple show/hide toggle without tracking concurrent requests. That works until two RPC calls overlap and the loading screen disappears too early.

Another issue is forgetting to decrement the counter in the failure path. If the spinner only hides on success, one failed request can leave the UI stuck in a permanent loading state.

A third pitfall is mixing local one-off spinners with a global loading manager inconsistently. Decide whether the spinner is application-wide or widget-specific and keep that policy coherent.

Finally, keep the centralized layer small. Its job is to manage loading state, not to become a giant RPC abstraction that hides too much application behavior.

Summary

  • Centralize GWT RPC loading behavior with a shared loading manager instead of repeating spinner code in every callback.
  • Track the number of active RPC calls so concurrent requests do not break the UI state.
  • Wrap AsyncCallback to ensure success and failure both update the loading state.
  • A service helper can enforce the pattern across the client codebase.
  • Keep the loading abstraction focused on UI state, not on unrelated business logic.

Course illustration
Course illustration

All Rights Reserved.