Building a GAE+GWT application using the best practices, Part 5

This is the fifth installment of the series “Building a GAE+GWT application using the best practices“.

In the last post of the series, I’ve shown how to setup a client side GIN+MVP project. This post will discuss the actual building of the UI and GWT-RPC web service calls using GWT-Dispatch on the client side.

Use cases

Before we dive into the code, let’s go over again the use cases our little app has.

  • When the UI loads, it should automatically fetch the latest saved rate
  • Also, the table below the latest rate should display the 10 most recent rates stored in the data store
  • The “Refresh” button does the above two steps
  • The “Fetch latest” button fetches the rate from the bank website, store it in the data store, and insert the latest rate in the recent rate table

MVP pattern

For traditional desktop application, there’s the MVC (Model-View-Control) pattern that’s been in existence for over 20 years, since the Smalltalk days. However, the responsibility separation between the view and controller hasn’t been well defined and over the years, people have been arguing about what whether the business logic should be entirely in controller.

With the MVP (Model-View-Presenter) pattern, the view itself doesn’t contain any logic. The presenter manipulates the view according to business logic. Therefore, the pattern is also called “supervising controller” or “passive view”. This brings a huge benefit because now unit tests only have to deal with presenters, and mock out the view through the display interface. With this setup, the presenter unit tests can be run entirely in JVM. Otherwise, the tests need to involve GWT widgets, which can only be tested with GWTTestCase, which takes a lot longer to run.

MainView

Here I’m using the new GWT2 UiBinder feature. UiBinder makes developing UI less boring.

MainView.ui.xml

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
	xmlns:g="urn:import:com.google.gwt.user.client.ui">
	<ui:style>
		.rateDisplay {
			font-size: x-large;
			font-weight: bolder;
		}
		
		.mainPanel {
			padding: 10px;
		}
	</ui:style>

	<ui:with type="ratechecker.client.bundles.RateCheckerClientBundle"
		field="bundle"></ui:with>
	<g:DecoratorPanel height="200px">
		<g:VerticalPanel styleName="{style.mainPanel}" verticalAlignment="ALIGN_TOP">
			<g:Label>Latest Selling Rate</g:Label>
			<g:Image resource="{bundle.loading}" visible="false"
				ui:field="loading"></g:Image>
			<g:Label ui:field="rateDisplay" styleName="{style.rateDisplay}"></g:Label>
			<g:HorizontalPanel>
				<g:Button ui:field="fetchLatest">Fetch Latest</g:Button>
				<g:Button ui:field="refresh">Refresh</g:Button>
			</g:HorizontalPanel>

			<g:Label>Recent rates</g:Label>
			<g:FlexTable ui:field="rateTable">
			</g:FlexTable>
		</g:VerticalPanel>
	</g:DecoratorPanel>
</ui:UiBinder> 

UiBinder can inter-operate with GWT2 ClientBundle. If you have used GWT1.x’s ImageBundle, ClientBundle is similar to that, except now with ClientBundle, other client resources are able to be bundled such as CSS stylesheet and external text resource.

MainPresenter.Display

The display interface is the only thing presenter knows about the UI, and the presenter operates/manipulates UI only through the display interface.

The display interface can be standalone, but I find it’s much more convenient to have it as an inner interface inside the presenter class.

	public interface Display extends WidgetDisplay {
		HasText getRateDisplayLabel();
		HasClickHandlers getFetchLatest();
		HasClickHandlers getRefresh();

		void setEnabledFetchLatestButton(boolean isEnabled);
		void setShowLoadingCurrentRate(boolean isLoading);
		/**
		 * Add the rate to the recent rate table.
		 * @param rate
		 * 		The {@link Rate} object
		 * @param toHead
		 * 		<code>true</code> - rate is inserted to the beginning of the table
		 * 		<code>false</code> - rate is appended to the end of the table
		 */
		void addToRecentRates(Rate rate, boolean toHead);
		/**
		 * Clear the recent rates table.
		 */
		void clearRecentRates();
	}

Here we use the “characteristic interface” of the UI elements as return type as they can be mocked. For things that cannot be returned as characteristic interfaces (like FlexTable), we provide methods for the presenter to manipulate the state of the UI objects (such as clearRecentRates()).

MainView.java

Now we have the display interface, we can map these interface methods onto our UI.

package ratechecker.client.mvp;

public class MainView extends Composite implements MainPresenter.Display {

	private static MainViewUiBinder uiBinder = GWT
	.create(MainViewUiBinder.class);

	interface MainViewUiBinder extends UiBinder<Widget, MainView> {
	}

	@UiField
	Button refresh;

	@UiField
	Button fetchLatest;

	@UiField
	Label rateDisplay;

	@UiField
	FlexTable rateTable;

	@UiField
	Image loading;

	private final DateTimeFormat _dateTimeFormat;

	@Inject
	public MainView(final DateTimeFormat dateTimeFormat) {
		_dateTimeFormat = dateTimeFormat;
		initWidget(uiBinder.createAndBindUi(this));
	}

	@Override
	public HasClickHandlers getFetchLatest() {
		return fetchLatest;
	}

	@Override
	public Widget asWidget() {
		return this;
	}

	@Override
	public HasText getRateDisplayLabel() {
		return rateDisplay;
	}

	@Override
	public void setEnabledFetchLatestButton(final boolean isEnabled) {
		fetchLatest.setEnabled(isEnabled);
	}

	@Override
	public void addToRecentRates(final Rate rate, final boolean toHead) {
		final int newRowIdx = toHead ? 0 : rateTable.getRowCount();
		rateTable.insertRow(newRowIdx);
		rateTable.setText(newRowIdx, 0, _dateTimeFormat.format(rate.getTimeFetched()));
		rateTable.setText(newRowIdx, 1, String.valueOf(rate.getRate()));
	}

	@Override
	public void clearRecentRates() {
		rateTable.removeAllRows();
	}

	@Override
	public HasClickHandlers getRefresh() {
		return refresh;
	}

	@Override
	public void setShowLoadingCurrentRate(final boolean isLoading) {
		loading.setVisible(isLoading);
		rateDisplay.setVisible(!isLoading);
	}

	@Override public void startProcessing() { }

	@Override public void stopProcessing() { }
}

A lot of this is boilerplate code to satisfy both UiBinder and GWT-presenter.Display interface. Ideally, the VIew shouldn’t do too much, if any at all. Realistically, this is harder to achieve.

MainPresenter

Finally, we can show you the presenter code.


public class MainPresenter extends WidgetPresenter<MainPresenter.Display> {

	private final DispatchAsync _dispatch;

	private final ILog _logger;

	@Inject
	public MainPresenter(final Display display, final EventBus eventBus, final DispatchAsync dispatch, final ILog logger) {
		super(display, eventBus);
		_dispatch = dispatch;
		_logger = logger;
	}

	@Override
	protected void onBind() {
		registerHandler(display.getFetchLatest().addClickHandler(new ClickHandler() {

			@Override
			public void onClick(final ClickEvent event) {
				fetchSellingRate();
			}

		}));

		registerHandler(eventBus.addHandler(RateFetchedEvent.TYPE, new RateFetchedHandler() {

			@Override
			public void onRateFetched(final Rate rate) {
				saveRate(rate);
			}

		}));

		registerHandler(eventBus.addHandler(RateSavedEvent.TYPE, new RateSavedHandler() {

			@Override
			public void onRateSaved(final Rate rate) {
				display.addToRecentRates(rate, true);
			}

		}));

		registerHandler(display.getRefresh().addClickHandler(new ClickHandler() {

			@Override
			public void onClick(final ClickEvent event) {
				getLatestSavedRates();
			}

		}));

		getLatestSavedRates();
	}

	void getLatestSavedRates() {
		display.setShowLoadingCurrentRate(true);

		final GetRates getRates = new GetRates();

		_dispatch.execute(getRates, new AsyncCallback<GetRatesResult>() {

			@Override
			public void onFailure(final Throwable caught) {
				display.setShowLoadingCurrentRate(false);
				_logger.error("Unable to get saved rates: " + caught.getMessage());
			}

			@Override
			public void onSuccess(final GetRatesResult result) {
				display.setShowLoadingCurrentRate(false);
				display.clearRecentRates();

				for (final Rate rate : result.getRates()) {
					display.addToRecentRates(rate, true);
				}

				// Put the latest rate in the box
				if (result.getRates().size() > 0) {
					final Rate latestRate = result.getRates().get(0);
					display.getRateDisplayLabel().setText(String.valueOf(latestRate.getRate()));
				}
			}
		});

	}

	void fetchSellingRate() {
		display.setShowLoadingCurrentRate(true);
		final CheckRate checkRate = new CheckRate(RateType.Selling);
		_dispatch.execute(checkRate, new AsyncCallback<CheckRateResult>() {

			@Override
			public void onFailure(final Throwable caught) {
				display.setShowLoadingCurrentRate(false);
				_logger.error("Unable to fetch rate: " + caught.getMessage());
			}

			@Override
			public void onSuccess(final CheckRateResult result) {
				display.setShowLoadingCurrentRate(false);
				// enable the fetch button
				display.setEnabledFetchLatestButton(true);
				display.getRateDisplayLabel().setText(String.valueOf(result.getRate().getRate()));
				eventBus.fireEvent(new RateFetchedEvent(result.getRate()));
			}

		});

		// disable the fetch button until RPC succeeds
		display.setEnabledFetchLatestButton(false);
	}

	void saveRate(final Rate rate) {
		final SaveRate saveRate = new SaveRate(rate);

		_dispatch.execute(saveRate, new AsyncCallback<SaveRateResult>() {

			@Override
			public void onFailure(final Throwable caught) {
				_logger.error("Unable to save rate: " + caught.getMessage());
			}

			@Override
			public void onSuccess(final SaveRateResult result) {
				eventBus.fireEvent(new RateSavedEvent(rate));
			}

		});

	}

	@Override protected void onPlaceRequest(final PlaceRequest request) { }

	@Override protected void onUnbind() {}

	@Override public void refreshDisplay() {}

	@Override public void revealDisplay() {}

	@Override public Place getPlace() { return null; }
}

In the binding process, the event handlers are attached to the view components. MainPresenter.bind() was explicitly called by AppPresenter.go(). This is a simple application with one presenter. If there are more presenters, AppPresenter needs to manage the state of these sub-presenters: if they’re active, the bind() method is called. If the presenter is no-longer active, the presenter’s unbind() method should be called to un-attach the handlers, so they don’t interfere with the event handlers that are currently in the active presenter.

The presenter is also responsible for making web service calls and deal with the returns. To call GWT-RPC web service using GWT-dispatch, we inject a DispatchAsync, which is an asynchronous counter part of the DispatchServlet introduced a few posts ago.

To call a web service, we simply construct an action object with required parameters and pass it in DispatchAsync.execute() and expect an AsyncCallback of type result that’s coupled with the action. (remember each action has a coupled result type). Also, in this application, every action has a related event to indicate whether the action is successful. The event is thrown onto the event bus, so any interested party can handle that. The main benefit of using event bus is that my web service calls don’t have to be coupled with the subsequent actions. For example, saveRate() method is responsible for making the web service calls, but the subsequent action (adding the saved rate to the recent rate table) isn’t part of saveRate() method, and it shouldn’t be. If in the future, some other actions need to be carried out when a rate is saved, we just have to add the action in the RateSavedHandler, and indeed, if another part of the UI (not visible by main presenter) need to do something after the rate is saved, that presenter only needs to handle that event in there without affecting saveRate() method at all.

For view the full source code, take a look at the project I created on GitHub.

EDIT:For any Google App Engine experts out there happened to be reading this post, I’m having trouble with the performance of this simple app. Seems like the data store is taking way too much time executing my query. Initially I thought it was because URL fetch is slow, but I recently added a property in Rate entity to track the time spent on fetching the URL and every request takes less than 1 second. However, the GetRates action takes a long time to return (usually ~3 to 5 seconds, sometimes even over 10 seconds). It’s a simple query ordering on a single property so no complex index is needed. So I’m wondering what’s wrong here.

Leave a comment

12 Comments

  1. e1

     /  April 8, 2010

    Hi

    Nice tutorial. I have been playing around gwt-Presenter for about a week now. I was wondering whether you can show the interplay between History Mechanism and gwt-Presenter’s Place API. I have written some codes that use the Place API. However, I am not sure whether I am using the API correctly or optimally. You can download my demonstration project of gwt-presenter project at http://code.google.com/p/mvp-gwt-adaptation/.

    Cheers,

    Reply
  2. This is very good tutorial. The only problem it uses many libraries I saw first time today. Anyway I’ll go and get something to broader my mind because it is impossible to fit all this stuff into overheated brain from first attempt. 🙂 This also forces me to come back to this tutorial until I get to understand all the stuff.
    Thanks for your time and insistency.
    P.S.: Sorry for my English.

    Reply
  3. Chris Worley

     /  May 29, 2010

    Hello,
    Nice tutorial. I’m trying to replicate your code in a separate project and I’m getting some errors with Part 5 of the tutorial. Here are the errors I’m getting:

    [ERROR] Errors in ‘ … RateCheckerGinjectorImpl.java’
    [ERROR] Line 34: Rebind result ‘ … .client.log.ILog’ must be a class
    [ERROR] Line 49: Rebind result ‘ … .client.mvp.Display’ must be a class

    [ERROR] Cannot proceed due to previous errors

    Any idea what is happening here? This is probably something really simple. Any help you can give would be appreciated.
    -Chris

    Reply
  4. Chris

     /  May 30, 2010

    I figured it out. It was something simple. I just needed to bind ILog and MainPresenter.Display to the RateCheckerClientModule. Thanks again.
    -Chris

    Reply
    • Glad you figured it out. I concur the error message given isn’t the most indicative. I found the best way to figure out what’s happening is to read the source code generated by GIN (or any deferred binding result for that matter). If you’re using maven, the generated source is under target/.generated

      Reply
  5. Chris

     /  May 30, 2010

    Nice tutorial. Much appreciated.

    Reply
  6. Vinay Soni

     /  March 14, 2011

    It is much simpler to Not use the MVP paradigm and still build complex UIs in a simple manner.

    What does one need in a typical UI application:

    1) Events
    2) Layout Managers
    3) Widgets
    4) Bindings

    Event mechanism with EventBus already allows communication across UI components
    spread out in the tree. This is where the complexity usually is – but it has been provided for.

    Already Editor mechanism takes care of Binding. UIBinder is also there.

    Why does one need to bring about the MVP separation? There is no complexity that needs to be prevented – unless you have additional views such as Phone view.

    It all feels over engineered.

    Reply
    • It’s deliberately over-engineered to illustrate the wiring of the pattern. Also, the main advantage of MVP is not for multiple views, but to isolate business logic in presenter so views can be mocked out during unit tests. Usually, the concrete views are GWT widgets which requires GWTTestCase — it’s heavy weight and takes a long time to start. MVP allows you to run most of your business logic tests in JUnit test cases, which runs inside a JVM.

      Reply
  7. Vinay Soni

     /  March 14, 2011

    One more:

    Also, what about nested Views. How would Presenter work with nested views as it is ultimately a Container/Component hierarchy?

    Reply
    • I don’t really have a generic answer for that. I think it’s sensible that if your sub-views are meant to be used across different screens, it justifies to be in its own presenter and let the parent presenter `bind` it and place it on the screen. Hope it helps.

      Reply
  1. Building a GAE+GWT application using the best practices (Index) « Reminiscential: of or pertaining to remembrance
  2. data mining best practices | DATA

Leave a reply to Vinay Soni Cancel reply