Sunday, December 11, 2011

GWT RPC (Part 3 of 3) - Take a ride on the client-side

GWT is all about creating web applications. And a web application wouldn't be a web application without client/server communication. A mechanism called GWT RPC (remote procedure call) is used for this communication in GWT. In this series of tutorials, I'm going to show you how GWT RPC works by building a simple GWT application that uses the Twitter API to perform tweet searches and retrieve the Twitter privacy policy.

The process involves three steps. I'll be covering step 3 in this blog post.

  1. Create the synchronous and asynchronous interfaces that define each RPC method
  2. Create the server-side implementation of each RPC method
  3. Create the UI (user interface) that the client will use perform the searches

You can download the entire sample project used in this tutorial here.


In this blog post, I'll show you how to design a basic UI that will call the RPC methods we created in the previous post. This UI will give us the ability to search for tweets and to view the Twitter privacy policy. Along with explaining how to call the GWT RPC methods from client code, I'll also give you a brief introduction into UI programming.

EntryPoint

package com.acme.twittersearch.client;

[imports...]

public class TwitterSearch implements EntryPoint {
[...]
  @Override
  public void onModuleLoad() {
    createWidgets();
    layoutWidgets();
  }
[...]
}

The first thing to notice is that the class implements the EntryPoint interface. Every GWT application must have one class that implements this. It tells GWT where to start when a user loads the application in her web browser. The interface consists of only one method, onModuleLoad(), which can be thought of as the GWT-equivalent of the public static void main(String args[]) method you see in command-line Java programs.

In our class, onModuleLoad() calls two private methods (that I've created), createWidgets() and layoutWidgets(). The createWidgets() method initializes our buttons, text boxes, etc. The second method, layoutWidgets(), adds those widgets to the page, positioning them in just the way that we want. You are not required by GWT to organize your code in this way, but I find it helpful to do so because UI code is very verbose and can quickly grow in size as your application becomes more complex.

Also, the fully-qualified class name of our entry point class must be added to the GWT module definition file (TwitterSearch.gwt.xml), as shown below:

<module rename-to='twittersearch'>
  [...]
  <entry-point class='com.acme.twittersearch.client.TwitterSearch'/>
  [...]
</module>

Widgets

I've defined my widget objects as class-level fields at the top of the class:

public class TwitterSearch implements EntryPoint {
  private Button privacyPolicyButton;
  private Button searchButton;
  private TextBox searchQueryTextBox;
  private Panel resultsPanel;
  private Label errorLabel;
  private Image loadingImage;
  [...]
}
  • privacyPolicyButton: This button will call the getPrivacyPolicy() RPC method and display the returned privacy policy on the page.
  • searchButton: This button will call the searchTweets() RPC method and display the search results on the page.
  • searchQueryTextBox: This is where the user will enter her Twitter search query.
  • resultsPanel: We'll display the results from each RPC call here.
  • errorLabel: If an error occurs while calling one of the RPC methods, we'll put the error message in here.
  • loadingImage: We'll display an animated loading icon when the client sends an RPC request to let the user know that their request is being processed. It's a good idea to always display some sort of loading image or message while an RPC request is being sent. Even the simplest requests can take several seconds to complete, especially if the user is on a slow network or the server is experiencing heavy load. In our case, we actually end up having to make two network calls per request, (1) the call to the GWT server and (2) the call to the Twitter server. So for us, having a loading image is essential (by the way, I got mine from loaderinfo.net).

The HTML page

Every GWT application has an HTML file that is loaded when the user navigates to the application in her browser. Here, I'm creating the "Twitter Search App" header and adding an image of the Twitter bird. I also have a CSS file which adds some extra formatting. The Javascript file that is being included is what the browser will use when the GWT application is compiled and run in production. The widgets that we load from our Java code will be added to the page dynamically at run time and be placed below the <h1> header (at the end of the <body> tag).

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <link type="text/css" rel="stylesheet" href="TwitterSearch.css">
    <title>Twitter Search App</title>
    <script type="text/javascript" language="javascript" src="twittersearch/twittersearch.nocache.js"></script>
  </head>

  <body>
    <img src="twitter-bird.png" align="right"/>
    <h1>Twitter Search App</h1>
  </body>
</html>
What the app will look like.

Calling the RPC methods

Calling the RPC methods involves us using the asynchronous interface we created back in step 1 of this blog series. You might be thinking, "But TwitterSearchServiceAsync is an interface and we never created an implementation class for it." You are correct. But this is actually not a problem--GWT uses a technique called "deferred binding", which means that GWT will create a implementation for us during runtime. This is done by calling the GWT.create() method:

public class TwitterSearch implements EntryPoint {
  private final TwitterSearchServiceAsync service = GWT.create(TwitterSearchService.class);
  [...]
}

Even though we want an instance of the asynchronous interface, we pass it the class object of the synchronous interface, TwitterSearchService. The method then returns an instance of the asynchronous interface, TwitterSearchServiceAsync.

Remember how each method in the asynchronous interface took an extra AsyncCallback parameter? We will be using this now. Let's write the code that will do the Twitter search:

searchButton = new Button("Search");
searchButton.addClickHandler(new ClickHandler() {
  @Override
  public void onClick(ClickEvent event) {
    setLoading(true);
    String query = searchQueryTextBox.getText();
    service.searchTweets(query, new AsyncCallback<List<Tweet>>() {
      @Override
      public void onFailure(Throwable caught) {
        errorLabel.setText(caught.getMessage());
        errorLabel.setVisible(true);
        setLoading(false);
      }

      @Override
      public void onSuccess(List<Tweet> result) {
        resultsPanel.clear();
        for (Tweet tweet : result) {
          SafeHtmlBuilder builder = new SafeHtmlBuilder();
          builder.appendHtmlConstant("<b>User: </b>");
          builder.appendEscaped(tweet.getFrom_user());
          builder.appendHtmlConstant("<br /><b>Created: </b>");
          builder.appendEscaped(tweet.getCreated_at());
          builder.appendHtmlConstant("<br /><b>Tweet: </b>");
          builder.appendEscaped(tweet.getText());
          builder.appendHtmlConstant("<br /><br />");
          resultsPanel.add(new HTML(builder.toSafeHtml()));
        }
        setLoading(false);
      }
    });
  }
});

As you can see, we add a ClickHandler to the search button to define what happens when the search button is clicked. When the button is clicked, we first set the page to a "loading" state by calling setLoading(true). This method disables the buttons so the user can't click them and displays the loading image. We then grab the search query from the search text box and make a call to the searchTweets RPC method using the asynchronous interface.

A loading animation appears when a RPC request is made.

The onFailure() method of the AsyncCallback class is called when the RPC method throws an exception. The thrown exception is passed in as an argument to the method. If this happens, we put the exception message in our error label so the user can get an idea of what went wrong. We then set the page back to its normal, "non-loading" state by calling setLoading(false), which will re-enable the buttons and hide the loading image.

An error is thrown when no search query is specified.

The onSuccess() method of the AsyncCallback class is called when the RPC method completes without error. Here, we clear the results panel of past searches, and then loop through the list of returned tweets, adding each one of them to the page. After all tweets are displayed, we call setLoading(false) to re-enable the buttons and hide the loading image.

Search results for "#christmas".

I'm using the SafeHtmlBuilder class here to generate the HTML that is used to display each tweet. This class is like the StringBuilder and StringBuffer classes in that it allows you to efficiently create a large string in a peace-meal fashion. However, SafeHtmlBuilder specializes in making strings of HTML code. The appendEscaped() method escapes all HTML special characters in the string before appending it (for example, converting all < characters to &lt;). The appendHtmlConstant() method does not escape HTML special characters, so you can use this to add HTML tags to the string.

The RPC method call for the getPrivacyPolicy() method follows the same pattern:

privacyPolicyButton = new Button("Privacy Policy");
privacyPolicyButton.addClickHandler(new ClickHandler() {
 @Override
 public void onClick(ClickEvent event) {
    setLoading(true);
    service.getPrivacyPolicy(new AsyncCallback<String>() {
      @Override
      public void onFailure(Throwable caught) {
        errorLabel.setText(caught.getMessage());
        errorLabel.setVisible(true);
        setLoading(false);
      }

      @Override
      public void onSuccess(String result) {
        resultsPanel.clear();

        // convert newlines to  <br / >
        SafeHtmlBuilder builder = new SafeHtmlBuilder();
        builder.appendEscapedLines(result);

        resultsPanel.add(new HTML(builder.toSafeHtml()));

        setLoading(false);
      }
    });
  }
});

I hoped you've enjoyed my three part series on GWT RPC. Creating and calling GWT RPC methods is very easy to do once you become familiarized with these three steps. Simply create the synchronous/asynchronous interfaces, create your server-side RPC implementation, and then call the RPC methods from the client using the asynchronous interface.

For more information on GWT, check out the GWT Developer's Guide.

1 comment:

Unknown said...

Thanks a lot for a detailed explanation of GWT-RPC mechanism.
I was looking for this from a long time and finally found it.
Thanks a lot for the source code too.
Keep up the good work.