21 August 2009

Google Web Toolkit (GWT) MVP Example

Here at Hive Development, I'm currently working on the GWT based UI for a new website/RIA monitoring service called SiteAlright. I recommend you head over and check it out. As you might expect we try to follow best practises when developing our apps and there's been quite a lot of talk recently on GWT Google Groups regarding the use of elements from the recent talk by Ray Ryan at Google I/O 2009 entitled Google Web Toolkit Architecture: Best Practices For Architecting Your GWTApp.

This excellent talk laid out several best practise approaches for architecting your GWT application based on the team's experiences while developing the new AdWords interface. Some of the key recommendations were:

The aim of this tutorial is to demonstrate these GWT best practises by applying them the default starter application generated by the Google Plugin for Eclipse.

There are many benefits to these patterns and I'll leave it to the links above to go into these in detail. However, using these patterns the bottom line is that your GWT application will be:

  • Easier to extend - well defined structure makes the addition of new business logic much easier;
  • Easier to test - the decoupling makes quick unit testing of commands, events and presenters not only possible but easy;
  • GWT's History mechanism is baked into your application from the start - this is crucial for a GWT application and is a pain to retro-fit;

MVP Web Application Starter Project

For this tutorial I'm using Eclipse with the Google Plugin for Eclipse. With the Google plugin when you create a GWT project, it also creates a useful starter application with some widgets and server components. This tutorial will work with this starter application and will apply the above patterns.

Although this example has all the source code and references to required resources, you may download the complete GreetMvp project for Eclipse 3.5 from here.

As well as the Google plugin, you'll also need the following GWT libraries:

GWT-PresenterAn implementation of the MVP pattern;
GWT-DispatchAn implementation of the command pattern;
Google GinDependency Injection based on Google's Guice;
GWT-LogA log4j-style logger for GWT.

NOTE: currently GIN needs to be built from source using SVN/Ant.

You'll also need the following libraries at the server:

log4jA logging framework;
Google Guice 2.0A dependency injection framework for Java.

After introducing these best practise patterns, the structure starter application will be transformed. At this point, it's probably worth noting that:

  • You'll no longer make RPC service calls directly - these are wrapped up in the command pattern and are handled by the gwt-dispatch library;
  • The main page and the server response popup will be separated into respective view/presenters;
  • The Event Bus pattern is implemented using the GWT 1.6+ Handlers mechanism.

Unsurprisingly, the code size will jump from the initial starter app, but what you'll end up with offers much more and will hopefully serve as the starting point for a real application based on these best practises.

Let's begin.

Fire up Eclipse and generate your starter application, I've called it GreetMvp:

google plugin eclipse create project 1google plugin eclipse create project 2

This will generate the following structure:

gwt default application structure screenshot

If you're not already familiar with the default application I suggest you take a look at the entry point class which in my case is co.uk.hivedevelopment.greet.client.GreetMvp.java. You'll see all the view code, custom handler logic and server calls all lumped into one method. Fire up the application and you see the following:

gwt default application screenshot

The first thing we'll do is add the required libraries to the project - at this point you should have downloaded the listed dependencies and built Google Gin.

Create a folder called lib in your project at the top level of your project for client-only libraries and add the following:

gin.jar

For server and client dependencies, add the remaining jars into the web application lib folder located at war/WEB-INF/lib:

aopalliance.jar(from Google Gin)
guice-2.0.jar(from Google Gin. IMPORTANT - use the version supplied with Gin and not Guice)
guice-servlet-2.0.jar(from Google Guice)
gwt-dispatch-1.0.0-SNAPSHOT.jar(from gwt-dispatch)
gwt-log-2.6.2.jar(from gwt-log)
gwt-presenter-1.0.0-SNAPSHOT.jar(from gwt-presenter)
log4j.jar(from log4j)

Add all of the above jars to the project's build path. You should have something that looks similar to this:

gwt default application structure screenshot

Edit the GWT module definition file co.uk.hivedevelopment.greet/GreetMvp.gwt.xml:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.7.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.7.0/distro-source/core/src/gwt-module.dtd">
<module rename-to='greetmvp'>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name="com.google.gwt.user.User" />
<inherits name="com.google.gwt.inject.Inject" />

<inherits name='net.customware.gwt.dispatch.Dispatch' />
<inherits name='net.customware.gwt.presenter.Presenter' />

<!-- Inherit the default GWT style sheet. You can change -->
<!-- the theme of your GWT application by uncommenting -->
<!-- any one of the following lines. -->
<inherits name='com.google.gwt.user.theme.standard.Standard' />
<!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
<!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/> -->

<!-- Specify the app entry point class. -->
<entry-point class='co.uk.hivedevelopment.greet.client.GreetMvp' />

<!-- Add gwt-log support, default level `OFF` - check for
extended property 'log_level' to see if this is overridden -->
<inherits name="com.allen_sauer.gwt.log.gwt-log-OFF" />

<!-- Also compile Logger at `INFO` level -->
<extend-property name="log_level" values="INFO" />
<set-property name="log_level" value="INFO" />

<!-- Turn off the floating logger - output will be shown in the
hosted mode console -->
<set-property name="log_DivLogger" value="DISABLED" />

<source path="shared" />
<source path="client" />
</module>

Note the <source> tags. We have roughly 3 top level packages defined: client, shared and server. We do not want GWT accessing the server sub-packages so we have explicity told the GWT compiler what it can access.

Try to compile the project, it should compile cleanly.

Create View and Presenters



Now we'll split the app into the following components:
AppPresenter.javaRepresents the main application;
GreetingView.javaThe GUI components for the greeting example;
GreetingPresenterThe business logic for the greeting example;
GreetingResponseViewThe GUI for the reponse popup;
GreetingResponsePresenterThe business logic for the response popup.
AppPresenter.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.dispatch.client.DispatchAsync;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.inject.Inject;

public class AppPresenter {
private HasWidgets container;
private GreetingPresenter greetingPresenter;

@Inject
public AppPresenter(final DispatchAsync dispatcher,
final GreetingPresenter greetingPresenter) {
this.greetingPresenter = greetingPresenter;
}

private void showMain() {
container.clear();
container.add(greetingPresenter.getDisplay().asWidget());
}

public void go(final HasWidgets container) {
this.container = container;

showMain();
}
}
GreetingView.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.presenter.client.widget.WidgetDisplay;

import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;

public class GreetingView extends Composite implements GreetingPresenter.Display {
private final TextBox name;
private final Button sendButton;

public GreetingView() {
final FlowPanel panel = new FlowPanel();

initWidget(panel);

name = new TextBox();
panel.add(name);

sendButton = new Button("Go");
panel.add(sendButton);

// Add the nameField and sendButton to the RootPanel
// Use RootPanel.get() to get the entire body element
RootPanel.get("nameFieldContainer").add(name);
RootPanel.get("sendButtonContainer").add(sendButton);

reset();
}

public HasValue getName() {
return name;
}

public HasClickHandlers getSend() {
return sendButton;
}

public void reset() {
// Focus the cursor on the name field when the app loads
name.setFocus(true);
name.selectAll();
}

/**
* Returns this widget as the {@link WidgetDisplay#asWidget()} value.
*/
public Widget asWidget() {
return this;
}

@Override
public void startProcessing() {
}

@Override
public void stopProcessing() {
}
}
GreetingPresenter.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.dispatch.client.DispatchAsync;
import net.customware.gwt.presenter.client.DisplayCallback;
import net.customware.gwt.presenter.client.EventBus;
import net.customware.gwt.presenter.client.place.Place;
import net.customware.gwt.presenter.client.place.PlaceRequest;
import net.customware.gwt.presenter.client.widget.WidgetDisplay;
import net.customware.gwt.presenter.client.widget.WidgetPresenter;
import co.uk.hivedevelopment.greet.shared.event.GreetingSentEvent;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreeting;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreetingResult;
import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.HasValue;
import com.google.inject.Inject;

public class GreetingPresenter extends WidgetPresenter {
/**
* The message displayed to the user when the server cannot be reached or
* returns an error.
*/
private static final String SERVER_ERROR = "An error occurred while "
+ "attempting to contact the server. Please check your network "
+ "connection and try again.";

public interface Display extends WidgetDisplay {
public HasValue getName();

public HasClickHandlers getSend();
}

public static final Place PLACE = new Place("Greeting");

private final DispatchAsync dispatcher;

// FUDGE FACTOR! Although this is not used, having Gin pass the object
// to this class will force its instantiation and therefore will make the
// response presenter listen for events (via bind()). This is not a very good way to
// achieve this, but I wanted to put something together quickly - sorry!
private final GreetingResponsePresenter greetingResponsePresenter;

@Inject
public GreetingPresenter(final Display display,
final EventBus eventBus,
final DispatchAsync dispatcher,
final GreetingResponsePresenter greetingResponsePresenter) {
super(display, eventBus);

this.dispatcher = dispatcher;

this.greetingResponsePresenter = greetingResponsePresenter;

bind();
}

/**
* Try to send the greeting message
*/
private void doSend() {
Log.info("Calling doSend");

dispatcher.execute(new SendGreeting(display.getName().getValue()), new DisplayCallback(display) {

@Override
protected void handleFailure(final Throwable cause) {
Log.error("Handle Failure:", cause);

Window.alert(SERVER_ERROR);
}

@Override
protected void handleSuccess(final SendGreetingResult result) {
// take the result from the server and notify client interested components
eventBus.fireEvent(new GreetingSentEvent(result.getName(), result.getMessage()));
}

});
}

@Override
protected void onBind() {
// 'display' is a final global field containing the Display passed into the constructor.
display.getSend().addClickHandler(new ClickHandler() {
public void onClick(final ClickEvent event) {
doSend();
}
});
}

@Override
protected void onUnbind() {
// Add unbind functionality here for more complex presenters.
}

public void refreshDisplay() {
// This is called when the presenter should pull the latest data
// from the server, etc. In this case, there is nothing to do.
}

public void revealDisplay() {
// Nothing to do. This is more useful in UI which may be buried
// in a tab bar, tree, etc.
}

/**
* Returning a place will allow this presenter to automatically trigger when
* '#Greeting' is passed into the browser URL.
*/
@Override
public Place getPlace() {
return PLACE;
}

@Override
protected void onPlaceRequest(final PlaceRequest request) {
// Grab the 'name' from the request and put it into the 'name' field.
// This allows a tag of '#Greeting;name=Foo' to populate the name
// field.
final String name = request.getParameter("name", null);

if (name != null) {
display.getName().setValue(name);
}
}
}
GreetingResponseView.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.presenter.client.widget.WidgetDisplay;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHTML;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class GreetingResponseView extends DialogBox implements GreetingResponsePresenter.Display {
private final Label textToServerLabel;
private final HTML serverResponseLabel;
private final Button closeButton;

public GreetingResponseView() {
setText("Remote Procedure Call");
setAnimationEnabled(true);

closeButton = new Button("Close");

// We can set the id of a widget by accessing its Element
closeButton.getElement().setId("closeButton");

textToServerLabel = new Label();
serverResponseLabel = new HTML();

final VerticalPanel dialogVPanel = new VerticalPanel();

dialogVPanel.addStyleName("dialogVPanel");
dialogVPanel.add(new HTML("Sending name to the server:"));
dialogVPanel.add(textToServerLabel);
dialogVPanel.add(new HTML("Server replies:"));
dialogVPanel.add(serverResponseLabel);
dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
dialogVPanel.add(closeButton);

setWidget(dialogVPanel);
}

public HasText getTextToServer() {
return textToServerLabel;
}

public HasHTML getServerResponse() {
return serverResponseLabel;
}

public HasClickHandlers getClose() {
return closeButton;
}

public DialogBox getDialogBox() {
return this;
}

/**
* Returns this widget as the {@link WidgetDisplay#asWidget()} value.
*/
public Widget asWidget() {
return this;
}

@Override
public void startProcessing() {
}

@Override
public void stopProcessing() {
}
}
GreetingResponsePresenter.java

package co.uk.hivedevelopment.greet.client.mvp;

import net.customware.gwt.presenter.client.EventBus;
import net.customware.gwt.presenter.client.place.Place;
import net.customware.gwt.presenter.client.place.PlaceRequest;
import net.customware.gwt.presenter.client.widget.WidgetDisplay;
import net.customware.gwt.presenter.client.widget.WidgetPresenter;
import co.uk.hivedevelopment.greet.shared.event.GreetingSentEvent;
import co.uk.hivedevelopment.greet.shared.event.GreetingSentEventHandler;
import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HasHTML;
import com.google.gwt.user.client.ui.HasText;
import com.google.inject.Inject;

public class GreetingResponsePresenter extends WidgetPresenter {
public interface Display extends WidgetDisplay {
HasText getTextToServer();

HasHTML getServerResponse();

HasClickHandlers getClose();

DialogBox getDialogBox();
}

public static final Place PLACE = new Place("GreetingResponse");

@Inject
public GreetingResponsePresenter(final Display display, final EventBus eventBus) {
super(display, eventBus);

bind();
}

@Override
protected void onBind() {
// Add a handler to close the DialogBox
display.getClose().addClickHandler(new ClickHandler() {
public void onClick(final ClickEvent event) {
display.getDialogBox().hide();

// Not sure of a nice place to put these!
// sendButton.setEnabled(true);
// sendButton.setFocus(true);
}
});

eventBus.addHandler(GreetingSentEvent.TYPE, new GreetingSentEventHandler() {

@Override
public void onGreetingSent(final GreetingSentEvent event) {
Log.info("Handling GreetingSent event");

display.getTextToServer().setText(event.getName());
display.getServerResponse().setHTML(event.getMessage());
display.getDialogBox().show();
}
});
}

@Override
protected void onUnbind() {
// Add unbind functionality here for more complex presenters.
}

public void refreshDisplay() {
// This is called when the presenter should pull the latest data
// from the server, etc. In this case, there is nothing to do.
}

public void revealDisplay() {
// Nothing to do. This is more useful in UI which may be buried
// in a tab bar, tree, etc.
}

/**
* Returning a place will allow this presenter to automatically trigger when
* '#GreetingResponse' is passed into the browser URL.
*/
@Override
public Place getPlace() {
return PLACE;
}

@Override
protected void onPlaceRequest(final PlaceRequest request) {
// this is a popup
}
}

At this point, we're still missing some code but hopefully you should start to see the structure coming together.

Events

Since this is a simple application, we only have one event - the GreetingSent event which has a corresponding handler:

GreetingSent.java

package co.uk.hivedevelopment.greet.shared.event;

import com.google.gwt.event.shared.GwtEvent;

public class GreetingSentEvent extends GwtEvent{
public static Type TYPE = new Type();

private final String name;
private final String message;

public GreetingSentEvent(final String name, final String message) {
this.name = name;
this.message = message;
}

public String getName() {
return name;
}

public String getMessage() {
return message;
}

@Override
public Type getAssociatedType() {
return TYPE;
}

@Override
protected void dispatch(final GreetingSentEventHandler handler) {
handler.onGreetingSent(this);
}
}
GreetingSentHandler.java

package co.uk.hivedevelopment.greet.shared.event;

import com.google.gwt.event.shared.EventHandler;

public interface GreetingSentEventHandler extends EventHandler {

void onGreetingSent(GreetingSentEvent event);

}

If you now look at the project references for the event and handler you can see where the events are fired and subsequently handled. The components are blissfully unaware of what produced the event and it just has the information that it needs to get the job done. Now imagine if you want to have another component that also reacts to this event, say to update another part of the GUI. Simple, just register another event handler - no spaghetti code.

RPC

Let's define client RPC. As mentioned earlier, we'll not be making the RPC calls directly. Instead we'll use the command pattern and let the gwt-dispatch library handle the underlying server calls. Although this is an implementation of the command pattern, it turns out that there is already a core GWT class called Command, so the authors of the gwt-dispatch library have opted to use Action instead - so "actions" are really "commands".

In our example, we'll define the Action SendGreeting which represent our server request and a SendGreetingResult class to encapsulate the server response:

SendGreeting.java

package co.uk.hivedevelopment.greet.shared.rpc;

import net.customware.gwt.dispatch.shared.Action;

public class SendGreeting implements Action {

private static final long serialVersionUID = 5804421607858017477L;

private String name;

@SuppressWarnings("unused")
private SendGreeting() {
}

public SendGreeting(final String name) {
this.name = name;
}

public String getName() {
return name;
}
}
SendGreetingResult.java

package co.uk.hivedevelopment.greet.shared.rpc;

import net.customware.gwt.dispatch.shared.Result;

public class SendGreetingResult implements Result {

private static final long serialVersionUID = 7917449246674223581L;

private String name;
private String message;

public SendGreetingResult(final String name, final String message) {
this.name = name;
this.message = message;
}

@SuppressWarnings("unused")
private SendGreetingResult() {
}

public String getName() {
return name;
}

public String getMessage() {
return message;
}
}

At this point, the client code should start to compile but there is still more to do before we can run it. We need some utility classes for the dispatcher and Gin:

CachingDispatchAsync.java

package co.uk.hivedevelopment.greet.client;

import java.util.HashMap;
import java.util.Map;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import net.customware.gwt.dispatch.client.DispatchAsync;
import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;

/**
* Dispatcher which support caching of data in memory
*
*/
public class CachingDispatchAsync implements DispatchAsync {
private DispatchAsync dispatcher;
private Map, Result> cache = new HashMap, Result>();

@Inject
public CachingDispatchAsync(final DispatchAsync dispatcher) {
this.dispatcher = dispatcher;
}

/*
* (non-Javadoc)
* @see net.customware.gwt.dispatch.client.DispatchAsync#execute(A, com.google.gwt.user.client.rpc.AsyncCallback)
*/
public , R extends Result> void execute(final A action, final AsyncCallback callback) {
dispatcher.execute(action, callback);
}

/**
* Execute the give Action. If the Action was executed before it will get fetched from the cache
*
* @param Action implementation
* @param Result implementation
* @param action the action
* @param callback the callback
*/
@SuppressWarnings("unchecked")
public , R extends Result> void executeWithCache(final A action, final AsyncCallback callback) {
final Result r = cache.get(action);

if (r != null) {
callback.onSuccess((R) r);
}
else {
dispatcher.execute(action, new AsyncCallback() {

public void onFailure(Throwable caught) {
callback.onFailure(caught);
}

public void onSuccess(R result) {
cache.put((Action) action, (Result) result);
callback.onSuccess(result);
}

});
}
}

/**
* Clear the cache
*/
public void clear() {
cache.clear();
}
}
GreetingClientModule.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.presenter.client.DefaultEventBus;
import net.customware.gwt.presenter.client.EventBus;
import net.customware.gwt.presenter.client.gin.AbstractPresenterModule;
import net.customware.gwt.presenter.client.place.PlaceManager;
import co.uk.hivedevelopment.greet.client.CachingDispatchAsync;
import co.uk.hivedevelopment.greet.client.mvp.AppPresenter;
import co.uk.hivedevelopment.greet.client.mvp.GreetingPresenter;
import co.uk.hivedevelopment.greet.client.mvp.GreetingResponsePresenter;
import co.uk.hivedevelopment.greet.client.mvp.GreetingResponseView;
import co.uk.hivedevelopment.greet.client.mvp.GreetingView;

import com.google.inject.Singleton;

public class GreetingClientModule extends AbstractPresenterModule {

@Override
protected void configure() {
bind(EventBus.class).to(DefaultEventBus.class).in(Singleton.class);
bind(PlaceManager.class).in(Singleton.class);

bindPresenter(GreetingPresenter.class, GreetingPresenter.Display.class, GreetingView.class);
bindPresenter(GreetingResponsePresenter.class, GreetingResponsePresenter.Display.class, GreetingResponseView.class);

bind(AppPresenter.class).in(Singleton.class);
bind(CachingDispatchAsync.class);
}
}
GreetingGinjector.java

package co.uk.hivedevelopment.greet.client.gin;

import net.customware.gwt.dispatch.client.gin.ClientDispatchModule;
import net.customware.gwt.presenter.client.place.PlaceManager;
import co.uk.hivedevelopment.greet.client.mvp.AppPresenter;
import com.google.gwt.inject.client.GinModules;
import com.google.gwt.inject.client.Ginjector;

@GinModules({ ClientDispatchModule.class, GreetingClientModule.class })
public interface GreetingGinjector extends Ginjector {

AppPresenter getAppPresenter();

PlaceManager getPlaceManager();

}

Now we tie all this together by replacing the existing module entry class with the following:

GreetMvp.java

package co.uk.hivedevelopment.greet.client;

import co.uk.hivedevelopment.greet.client.gin.GreetingGinjector;
import co.uk.hivedevelopment.greet.client.mvp.AppPresenter;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.RootPanel;

public class GreetMvp implements EntryPoint {
private final GreetingGinjector injector = GWT.create(GreetingGinjector.class);

public void onModuleLoad() {
final AppPresenter appPresenter = injector.getAppPresenter();
appPresenter.go(RootPanel.get());

injector.getPlaceManager().fireCurrentPlace();
}
}

Server Components

Okay, now let's define the server side. We need to configure Guice and the dispatch handler plus we need to provide an implementation for the SendGreeting action.

SendGreetingHandler.java

package co.uk.hivedevelopment.greet.server.handler;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import net.customware.gwt.dispatch.server.ActionHandler;
import net.customware.gwt.dispatch.server.ExecutionContext;
import net.customware.gwt.dispatch.shared.ActionException;
import org.apache.commons.logging.Log;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreeting;
import co.uk.hivedevelopment.greet.shared.rpc.SendGreetingResult;
import com.google.inject.Inject;
import com.google.inject.Provider;

public class SendGreetingHandler implements ActionHandler<SendGreeting, SendGreetingResult> {
private final Log logger;
private final Provider<ServletContext> servletContext;
private final Provider<HttpServletRequest> servletRequest;

@Inject
public SendGreetingHandler(final Log logger,
final Provider<ServletContext> servletContext,
final Provider<HttpServletRequest> servletRequest) {
this.logger = logger;
this.servletContext = servletContext;
this.servletRequest = servletRequest;
}

@Override
public SendGreetingResult execute(final SendGreeting action,
final ExecutionContext context) throws ActionException {
final String name = action.getName();

try {
String serverInfo = servletContext.get().getServerInfo();

String userAgent = servletRequest.get().getHeader("User-Agent");

final String message = "Hello, " + name + "!<br><br>I am running " + serverInfo
+ ".<br><br>It looks like you are using:<br>" + userAgent;


//final String message = "Hello " + action.getName();

return new SendGreetingResult(name, message);
}
catch (Exception cause) {
logger.error("Unable to send message", cause);

throw new ActionException(cause);
}
}

@Override
public void rollback(final SendGreeting action,
final SendGreetingResult result,
final ExecutionContext context) throws ActionException {
// Nothing to do here
}

@Override
public Class<SendGreeting> getActionType() {
return SendGreeting.class;
}
}
DispatchServletModule.java

package co.uk.hivedevelopment.greet.server.guice;

import net.customware.gwt.dispatch.server.service.DispatchServiceServlet;
import com.google.inject.servlet.ServletModule;

public class DispatchServletModule extends ServletModule {

@Override
public void configureServlets() {
// NOTE: the servlet context will probably need changing
serve("/greetmvp/dispatch").with(DispatchServiceServlet.class);
}

}
LogProvider.java

package co.uk.hivedevelopment.greet.server.guice;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.Log4JLogger;
import com.google.inject.Provider;
import com.google.inject.Singleton;

@Singleton
public class LogProvider implements Provider<Log>{

public Log get() {
return new Log4JLogger("GreetingLogger");
}

}
MyGuiceServletConfig.java

package co.uk.hivedevelopment.greet.server.guice;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;

public class MyGuiceServletConfig extends GuiceServletContextListener {

@Override
protected Injector getInjector() {
return Guice.createInjector(new ServerModule(), new DispatchServletModule());
}
}
ServerModule.java

package co.uk.hivedevelopment.greet.server.guice;

import net.customware.gwt.dispatch.server.guice.ActionHandlerModule;
import org.apache.commons.logging.Log;
import co.uk.hivedevelopment.greet.server.handler.SendGreetingHandler;
import com.google.inject.Singleton;

/**
* Module which binds the handlers and configurations
*
*/
public class ServerModule extends ActionHandlerModule {

@Override
protected void configureHandlers() {
bindHandler(SendGreetingHandler.class);

bind(Log.class).toProvider(LogProvider.class).in(Singleton.class);
}
}

Finally we need to reconfigure the web.xml with the following:

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

<!-- Remote logging agent for gwt-log -->
<servlet>
<servlet-name>remoteLoggerServiceImpl</servlet-name>
<servlet-class>com.allen_sauer.gwt.log.server.RemoteLoggerServiceImpl</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>remoteLoggerServiceImpl</servlet-name>
<url-pattern>/greetmvp/gwt-log</url-pattern>
</servlet-mapping>


<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<listener>
<listener-class>co.uk.hivedevelopment.greet.server.guice.MyGuiceServletConfig</listener-class>
</listener>

<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>GreetMvp.html</welcome-file>
</welcome-file-list>

</web-app>

You may now launch the application. Surprise surprise, it looks no different but you're now following best practises. Good luck with your own GWT MVP development.

gwt default application screenshot (after)

114 comments:

  1. Thank you very much for this write up!
    I was trying to get my head around a lot of these things and this sure does help.

    Cheers,
    Yesudeep.

    ReplyDelete
  2. How does the back/forward button work with this?

    When you click the "Go" button, does the url update to be GreetMvp.html#name=Chris? It doesn't appear to have done that in your example.

    ReplyDelete
  3. That's the longest Hello World I've ever seen!

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. We are just implementing all of this. Fantastic right up !!

    It'll help a lot.

    ReplyDelete
  6. Thanks. Two remarks:

    1. The code in the blog is not syntactically correct in some places (missing "<"s at least).

    2. There are no unit tests. How can it be a best practice if there is no tests ;) ?

    Cheers,

    S.

    ReplyDelete
  7. Thank you all for your feed back.

    @brendan - You're right, I'm missing a hook. I'll provide a post soon (in day or so) covering this;

    @Ricardo - lol, sorry it was a bit of a long one! Thankfully, adding functionality is much quicker.

    @Stefane - I did have a bit of a wrestle with Blogger and the SyntaxHighlighter library to get things displaying. You can download the project complete from a link near the top of the post. Agreed about the unit tests. I want to add those in another post as this post was long enough already - I'll try to get something done this week.

    ReplyDelete
  8. There is a unstated dependency on the event bus library. Where is it available?

    ReplyDelete
  9. @arouse EventBus is defined as part of the gwt-presenter library.

    ReplyDelete
  10. I think everyone should remember that every pattern is appropriate to a particular use case or problem that you are trying to solve. Hello world unfortunately possesses none of these characteristics.
    For example, if you have multiple event sources and multiple event targets for one event, an event bus (or any other Observer pattern implementation) might be in order. Not the case here.
    Likewise MVP has a place where one might switch model, view or presenter. Otherwise, keep the view and model and eliminate the presenter.
    I really think what may be appropriate for Google Wave is a bit of overkill for the run of the mill CRUD application screen.

    ReplyDelete
  11. I keep getting this error: "[ERROR] Type 'net.customware.gwt.dispatch.shared.Action<?>' was not serializable and has no concrete serializable subtypes"...Any idea what might be wrong here? I'm using gwt-dispatch from its Maven repository on Google code.

    ReplyDelete
  12. @tony - thanks for the comment. You're right, MVP is overkill for an app of Hello World size and I agree that using patterns for patterns sake is often a bad idea.

    This post was just meant to demonstrate the necessary components for MVP in the smallest form possible. When I was trying to get started with MVP I found myself spending a lot of time in Google trying to stitch these principles together, hence I thought some people may find this post useful. The actual app that I'm working on is must larger in scope and MVP is very appropriate.

    If you're looking for a larger example then take a look at Hupa "GWT based Webmail for IMAP-Servers" in Apache Labs:

    http://svn.apache.org/repos/asf/labs/hupa/

    To be honest though, I'm finding that separating code into view and presenter, using DI and commands are working out pretty well and I would actually recommend it for anything but the simplest of apps. Obviously that's my taste. I used to have much of the GWT app code lumped together as composite classes and that got messy very quickly - the spaghetti situation mentioned in the Google I/O talk rang very true to me. Life is definitely much sweeter now.

    ReplyDelete
  13. @Kevin - I'm not sure what's going on there. Have you tried downloading the complete project from the link towards the start of this post?

    http://static1.hivedevelopment.co.uk/blog/gwt-best-practise-mvp-di-command/GreetMvp.zip

    ReplyDelete
  14. Chris: the reported error is with gwt-dispatch, not your code. I think it may have something to do with my compiler settings, though I'm not sure. I'll play around with it more.

    Also, I have a question regarding efficiency. The injector initializes everything at startup (through binding, am I right?) and the views are initialized with the presenter, but not all views are going to be used in a larger application. How does MVP tackle this problem? (I know it's not a problem for HelloWorld, but when I look at Hupa, it initializes all the widgets in the view's constructor)

    Maybe I'm missing something here?

    Thanks for the writeup.

    ReplyDelete
  15. @Kevin - if you're struggling with gwt-dispatch there is a version supplied with the project link which was built from source last Thursday.

    The injector does indeed initialise everything so you're quite right - when projects get large this can become a problem with unused presenters gobbling resource. Here you could consider moving widget creation code from the c'tor into a method can be called lazily. In the presenter instead of accessing 'display' directly, you could override BasicPresenter.getDisplay and add a call to Display.startProcessing() before returning the display reference. I couldn't see where the start/stopProcessing hooks are called by the presenter library (and their intent is not clear), so I suppose you could use them to manage your lazy initialisation code? Or extend WidgetDisplay to define your own.

    Also, the presenter defines revealDisplay() but that is only called if using the Place logic which may not be appropriate if a page is composed of several presenters (only one can be called). For example, call the example using the following URL and refresh:

    http://localhost:8080/GreetMvp.html#Greeting;name=Foo

    As we've specified #Greeting, this will cause the GreetingPresenter's revealDisplay to be called.

    Good luck.

    ReplyDelete
  16. All that is left now is to integrate the "Gateway"-Pattern mentioned in another talk at I/0 2009 that eases the building of "GWT.runAsync"-Modules.

    ReplyDelete
  17. @Kevin: This error is commonly output by GWT when one of the following is true:

    1. Your Action/Result classes are not serializable. The most common causes are not having an empty constructor and having non-serializable fields in the class.
    2. You are not actually using any Action/Result instances anywhere in your code, so GWT doesn't know about any valid subclasses.

    Not sure if either of these might be the case for you.

    ReplyDelete
  18. Thanks for this nice example! I have a question i hope not too stupid (i'm new to all this, and not very good in english) :

    It seems that the "google getting started" way to implement a GWT-RPC is not used anymore. So we can forget about client-side Service/ServiceAsync interfaces + server-side ServiceImpl (always present in you example) and benefit from the command pattern. But what if i want to use Spring Security + Hibernate and Gilead for client-side persistent ejb? I have trouble to implement this in the server-side Handler of my services : Before that, i had a ServiceImpl with Gilead bean manager+Persistent hibernate, extending a DI (Spring) class for Security and Spring, extending a Persistence Class (Gilead), finally extending the RemoteServiceServlet.
    i just followed/blend Wah Cheng blog : http://seewah.blogspot.com/2009/02/gwt-and-spring-security.html and Gilead ... but now i can't get it to work with this new command pattern way. Is there any other way to secure and persist ejb with gilead with command pattern?

    ReplyDelete
  19. @David - cheers for the input mate, appreciated;

    @Alcor - I hope to add a few more articles soon on unit tests and maybe try to add something on history management. However one thing I've had to do very recently on my current project is integrate the command pattern with JBoss Seam at the server where previously I was using plain old RPC. You'll be pleased to hear that the conversion was fairly straight forward and I'm sure you could use many of the steps I used as base for integrating with Spring? I'll try to add a quick article this evening while it's fresh in my mind.

    The result passed back from a command execution can be anything you like as long as it's serialisable and implements the Result marker interface. This includes Hibernate objects. But BE WARNED that serialising Hibernate objects directly is a bad idea where cross graph dependencies, proxied collections and Timestamps are amongst some of the things waiting to bite you. Here you have a couple of options: use DTOs or process the Hibernate objects in some way before sending them over the wire. There's a book called "Pro Web 2.0 Application Development with GWT" by Jeff Dwyer that presents a solution to the latter problem by way of a Hibernate filter through which you can prepare a Hibernate object for serialisation. Full source of the book's application is online:

    http://github.com/jdwyah/tocollege.net/blob/f97ed4ff3f31c1c463eb426c34a55de439c601b3/src/main/java/com/apress/progwt/server/gwt/HibernateFilter.java

    The book also uses Spring MVC + Acegi security for its data services so it's probably worth having a good poke around that project.

    I hope that's useful.

    C.

    ReplyDelete
  20. Thanks for your help and advices. I'll read carefully the git and at your forthcoming article.

    NB : I just saw the GWT-dispatch code with now Std/SecureDispatchServletModule. Is it to integrate or emulate a security scheme?

    ReplyDelete
  21. How is it best to get rid of the "FUDGE FACTOR" ?

    ReplyDelete
  22. @Craig - good question, it was only a matter of time before I was pulled up on that ;)

    In the GreetMvp example the fudge factor was to inject the GreetingResponsePresenter in order for Gin to create an instance which is bound to the GreetingSentEvent. I think the approach should have been to let the GreetingPresenter take care of these events and control the showing of the popup.

    I've been experimenting with a solution for pop-ups which probably needs a post of its own. Basically I created a PopupDelegateView/Presenter which wraps a regular View/Presenter to take care of common pop-up related stuff - so you can show a view as a pop-up or inline. I'm holding off posting something until I've given it more of a test, but it shouldn't be too long (a week or so).

    Of course, if anyone else has some suggestions on pop-ups then I'd love to hear about them.

    I hope that helps a bit?

    ReplyDelete
  23. Hi Chris thanks for the reply

    What am I am trying to achieve is the following....

    I have a login event and handerer, on return from I wish to raise 2 events (maybe more) that other parts of the system listen to ie a message handler etc. I cant see how I can make multiple presenters listen to the same event?

    Is this possible?

    Thanks

    Craig

    ReplyDelete
  24. Morning Craig,

    That's certainly possible, in fact it's one of the main benefits of using the event based approach. Presumably you have some form of login action? All you should have to do is issue the necessary events on login success:

    private void doSend() {
    dispatcher.execute(new SendGreeting(display.getName().getValue()), new DisplayCallback(display) {

    @Override
    protected void handleFailure(final Throwable cause) {
    ...
    }

    @Override
    protected void handleSuccess(final LoginResult result) {
    eventBus.fireEvent(new MyPostLoginEvent(result.getSomeResult()));
    ... // more events?
    }

    });
    }



    Then simply add a handler in each presenter where you want to observe these events:

    eventBus.addHandler(MyPostLoginEvent.TYPE, new MyPostLoginEventHandler() {

    @Override
    public void onGreetingSent(final MyPostLoginEvent event) {
    Log.info("Do post login processing here");
    }
    });


    An off the top of my head troubleshooting list for why some of the events might not fire:

    Has each presenter been setup in your Gin client module?
    Does the constructor for each presenter call bind()?
    Does bind() subscribe to the specfied event?

    Good luck.

    ReplyDelete
  25. Slight edit to last response:

    I forgot to change doSend and the SendGreeting to better illustrate the point. They should have been

    private void doLogin() {
    dispatcher.execute(new LoginAction(display.getName().getValue(), display.getPassword().getValue())), new DisplayCallback(display) {

    ...

    }

    ReplyDelete
  26. @Alcor - I've not looked into those additional classes since I'm bypassing the gwt-dispatch servlet modules altogether. I intend to use regular Seam security for my application.

    I've now posted the instructions for the Seam integration here:

    http://blog.hivedevelopment.co.uk/2009/08/integerating-gwt-dispatch-with-jboss.html

    Hopefully that will give you enough of a starting point to adapt a Spring version?

    Good luck,

    C.

    ReplyDelete
  27. Very, very helpful. Thank you for posting this! Keep 'em coming!

    ReplyDelete
  28. Are we sure CachingDispatchAsync is actually being used here in lieu of the default dispatcher?

    I tried making some changes to it, but they didn't show up in execution. I then went back to basics and just added some logging statements to the CachingDispatchAsync's execute method, but it doesn't get called either.

    I don't fully understand how Gin injection works so I can't really fully see what's going on or whether the CachingAsyncDispatch class is getting used or not. Can anyone advise? Would be very helpful.

    ReplyDelete
  29. Thank you very much for this post. Regarding the fudge factor, would it be feasible to inject all the presenters in the appPresenter in an effort to keep the fudge centralized, or do you see any problems in that approach?

    What part of the application would you make responsible for hiding old views? The presenter that fires an event that triggers the display of a new view, or the presenter that will display the new view? Or perhaps the presenter of the view that contains the two views?

    ReplyDelete
  30. Thanks for posting this nice tutorial! I have been messing with it today to use Spring on the server side. I've documented what I did here.

    ReplyDelete
  31. This article should be rated PG-21 . its meant for mature GWT developers who have at one point encountered the issued being addressed here. I am one of them and it has really helped. Kind regards.

    Joshua.

    ReplyDelete
  32. @aaa (if that is your real name ;)

    Guilty as charged. Unfortunately CachingDispatchAsynch isn't a plug in replacement for the default DispatchAsynch implementation so you'll need to refer to it explicitly in your presenters. To use CachingDispatchAsynch, update the necessary presenters as follows:

    1. Update the c'tor parameters to use the CachingDispatchAsynch along with the associated class property type;
    2. Change calls to dispatcher.execute() to dispatcher.executeWithCache().

    Cheers,

    C.

    ReplyDelete
  33. @sorenbs - it's probably not advisable to inject all presenters into appPresenter. I have come up with a better solution to the original popup issue, so in the case of the example it will eliminate the need for the fudge factor. I'll try and post something very soon.

    I've yet to properly implement lazy loading (and therefore associated tear down) code of my views. I believe that the whole issue of lazy loading is being looked at for the next version of gwt-presenter so I'll be looking to that when I tackle my own views. It shouldn't be too long before I look at this mind.

    Cheers,

    C.

    ReplyDelete
  34. @Peter - thanks for the link, that should be useful to quite a few people.

    @Josh - nice!

    ReplyDelete
  35. Hi and thx for this great things,
    About the FUDGE factor:
    What about binding the Response presenter asEagerSingleton ?


    Regards.

    ReplyDelete
  36. I tried a customized version of this and got the following error:
    ERROR] Deferred binding result type 'net.customware.gwt.dispatch.client.DispatchAsync' should not be abstract

    Any ideas?

    ReplyDelete
  37. I ran into this same problem - I can't say for sure what the issue is in your case; I had neglected to include the GinModules annotation on the GreetingGinJector.

    Unfortunately, you're probably going to have to diff your rewritten code against the sample project to determine the exact source of the problem.

    ReplyDelete
  38. This comment has been removed by a blog administrator.

    ReplyDelete
  39. Following your excellent instructions and example code here, I was able to wire in gwt-presenter and gwt-dispatch to my existing GWT + AppEngine project in less than a day. Even more importantly, it ran correctly the FIRST TIME. I can't thank you enough for putting this all together. I wanted to use MVP per the Google I/O talk, and this has made it 100x easier.

    ReplyDelete
  40. What was the reason behind placing gin.jar inside of root-level lib folder? After I moved this jar to standard location in "war/WEB-INF/lib" it seems that it made no difference to how application runs...

    ReplyDelete
  41. Hi,

    I would like to ask, how would you suggest for me to handle the history in this situation:
    1. There is a MainPresenter, which presents in a DockPanel any other presenter in my application.
    2. Although, it seems to me that, because the mainPresenter is always visible, the OnPlaceRequest is only called within its place. It's not called on the Presenter inside the DockPanel.

    Is my use of the MVP wrong? How would you suggest me to correct this?

    Thanks a lot!

    Marc

    ReplyDelete
  42. Oh. Sorry, I just realized it it's in fact calling the correct OnPlaceRequest of each presenter.
    =)

    Thanks, anyways! Your post is enlightening!

    ReplyDelete
  43. No, I am wrong again... lol

    Sorry to disturb.. My MVP is not functioning properly using this pattern I told you above.

    I'd be glad If I could receive any suggestions.. =)

    Thanks

    Marc

    ReplyDelete
  44. Thank you for putting this together; it really simplified getting gin and guice running with MVP for me. Using your demo I was able to get going very quickly!

    I look forward to seeing your solution for the fudge factor :)

    ReplyDelete
  45. Hi, Thanks for your work. I managed to get it working. When trying to mod it and extend it for my own use -- to add a new view, Pattern and RPC service -- I got stuck. Could someone give me an example of this? Thanks again,
    James

    ReplyDelete
  46. Excellent tutorial. Found some @Overrides that needed to be deleted, but otherwise very clean.

    Minor UI tweak: GreetingResponseView extends DialogBox. If you notice in the boilerplate Greeting from Google, the last line centers the dialog box. So, after 'setWidget(dialogVPanel);' in GreetingResponseView, add 'this.center();'

    ReplyDelete
  47. Sorry for the delay in replying.

    @Gregory - this was just to get rid of warnings like these:

    The following classpath entry 'blah' will not be available on the server's classpath

    I realise that you can right click on the warnings and "quick fix" them to be ignored.


    @Marcalc - if you're still having problems it's probably best that you post your questions to the gwt-presenter group:

    http://groups.google.com/group/gwt-presenter

    @Syntax - thanks! I haven't really come up with a better solution to the fudge factor other than maybe using asEagerSingleton() as suggested by NarZo above.

    @jaga - you may need to post to the gwt-presenter (re: new view/presenter) and gwt-dispatch (re: new RPC) groups.

    http://groups.google.com/group/gwt-presenter
    http://groups.google.com/group/gwt-dispatch

    @Stuart - thanks for the feedback.

    ReplyDelete
  48. Thanks very much for writing this.

    ReplyDelete
  49. Do you have an archive of a working sample that includes all the libraries for us to download? I am having trouble with the gin libraries.

    ReplyDelete
  50. @Sujay - you can download an Eclipse project complete with dependencies from here:

    http://static1.hivedevelopment.co.uk/blog/gwt-best-practise-mvp-di-command/GreetMvp.zip

    ReplyDelete
  51. how can this be used with RequestBuilder

    ReplyDelete
  52. @Randy - AFAIK RequestBuilder is not currently supported directly by gwt-dispatch. Here is a discussion might get you started:

    http://groups.google.com/group/gwt-dispatch/browse_thread/thread/b87dca66d886fdbc/cf0188239903b636?lnk=gst&q=RequestBuilder#cf0188239903b636

    ReplyDelete
  53. This comment has been removed by the author.

    ReplyDelete
  54. @Oliver - sounds like you're missing the guice-2.0.jar. Make sure it's the version that ships with your version of GIN.

    ReplyDelete
  55. Hi,

    That's why I removed my comment, I figured out why I get this error. But now, I got this 2 error
    [ERROR] Line 57: Rebind result 'net.customware.gwt.dispatch.client.DispatchAsync' must be a class
    [ERROR] Line 114: Rebind result 'net.customware.gwt.presenter.client.EventBus' must be a class
    I change a bite the code to match my sample app. but I kept the declaration and usage of this two classes like in the tutorial.

    ReplyDelete
  56. This comment has been removed by the author.

    ReplyDelete
  57. @Oliver - not sure what happened there, looks like some GIN config is not quite right.

    @Paatal - you're right - they're not needed. They were left over from the generated starter application and I forgot to remove them - my bad, sorry.

    ReplyDelete
  58. First of all thank you for all you efforts.
    suppose we want to execute a command on the client side, so we need a handler implementation on the client side. it can have the undo effect too. My question is where are we supposed to bind this handler? the dispatcher is of type DispatchAsynch and I think it is only for actions that have a handler on the server.

    ReplyDelete
  59. @Meysam - AFAIK gwt-dispatch is for commands that are executed at the server. However, you may wish to get a more informed answer to this by posting on the gwt-dispatch group.

    ReplyDelete
  60. Hello, everyone is interested in printable and readable version of this conference, please look
    http://extgwt-mvp4g-gae.blogspot.com/2009/10/gwt-app-architecture-best-practices.html

    ReplyDelete
  61. Hi,

    I am fighting for a while on this. I get "java.lang.RuntimeException: Deferred binding failed for 'com.greeting.client.mvp.GreetingView' (did you forget to inherit a required module?)"
    Do you think this can be cause by the version of Java I am using ? Do you have a war version of your code, because I am force to change your code for compilation. Does I have to specify all the template like : WidgetPresenter<GreetingPresenter.Display> this change solve a lot of issue on my side. I am not a Java specialist. Thanks for your answer.

    ReplyDelete
  62. @Oliver - a complete project is down-loadable from here:

    http://static1.hivedevelopment.co.uk/blog/gwt-best-practise-mvp-di-command/GreetMvp.zip

    The "Deferred binding failed" error can sometimes come from resolving the wrong package for a class - for example I'm often caught out if Eclipse resolves the wrong kind of Log, say:

    import org.mortbay.log.Log;

    instead of:

    import com.allen_sauer.gwt.log.client.Log;

    Cheers,

    Chris.

    ReplyDelete
  63. Thanks, for your answer, finaly I discover that using JDK 1.6 is working for your project without any changes :).
    Then my issue was stupid I forget to add placeholder in the html page, but error was not very clear :).

    regards

    Olivier

    ReplyDelete
  64. This looks great and I'd like to run it from Eclipse, but 1) I get errors by many of your override annotations, which 2) if I simply remove, result in some module loading error when I launch this as a Google Web App.

    ReplyDelete
  65. Bummer... I don't think this will work on OS X 10.5 with JVM 1.5 or JVM 1.6 (-d32). At least, I couldn't get it to work.

    Challenges faced:
    Project doesn't support JVM 1.5, so I tried JVM 1.6... and on 10.5, I don't think JVM 1.6 supports a 32 bit mode.

    Anyone else successful?

    ReplyDelete
  66. I had some problems running the linked project in OSX 10.5.7.

    I turns out that GWT hosted mode requires JDK 1.5 here. The problem was that gin.jar (the only lib actually compiled from source by the article authtor) was compiled with JDK 1.6.

    I thought I should mention this for others on this OS.

    ReplyDelete
  67. Thanks for this code - it helped me understand. I think it would of been better if some of the unnecessary code was removed such as GreetingView.java's RootPanel.get("foo").add(name) and the GreetingService.java file.

    ReplyDelete
  68. Hi, great tutorial.
    So this event bus is used only on the client gwt side to transfer event messages to the "subscribed" views?
    For example if i have some custom slider and i want to update several other ui components on my screen when the slider moves this is the perfect solution? And in that way the ui components wouldn't know about the slider.

    Also could you place another link for this "hupa" project because the above is broken.

    And my last question for now:
    Is this sample hello world project configuration and setup ready for real project usage? I asj because i do not know any thing about gin, juice, gwt-dispatch and gwt-presenter, and i am going to implement some small project with gwt in the next month and i would like to try this architecture, but i am afraid that i will not have much time to investigate in the above libraries.

    Regards and again great work.

    ReplyDelete
  69. I have created a skeleton app, for anybody who want's to save few minutes. It uses all the above stuff, but instead of Javish serialized RPC, it uses Restlet GWT library, and jruby + Sinatra backend.

    http://github.com/skrat/sinwar

    ReplyDelete
  70. @bot - sorry for late reply.

    > So this event bus is used only on the client gwt side to transfer event messages to the "subscribed" views?

    Yes.

    > For example if i have some custom slider and i want to update several other ui components on my screen when the slider moves this is the perfect solution? And in that way the ui components wouldn't know about the slider.

    Yes exactly.

    > Also could you place another link for this "hupa" project because the above is broken.

    This project was moved a short while ago to here:

    http://james.apache.org/hupa/index.html

    >And my last question for now:
    >Is this sample hello world project configuration and setup ready for real project usage? I asj because i do not know any thing about gin, juice, gwt-dispatch and gwt-presenter, and i am going to implement some small project with gwt in the next month and i would like to try this architecture, but i am afraid that i will not have much time to investigate in the above libraries.


    I used it for my project and can recommend the MVP architecture. As various comments above point out, there are some old artifacts lying around the project which can be deleted. Please be advised that the gwt-presenter project is undergoing a bit of an overhaul at the minute so my code might need adjusting if you upgrade the libraries. That said - you should still be able to get a lot done with the version supplied with the project.

    All the best,

    Chris.

    ReplyDelete
  71. // NOTE: the servlet context will probably need changing
    serve("/greetmvp/dispatch").with(DispatchServiceServlet.class);

    I don't really understand these code lines.
    Where can I configure that the RPC commands sent from the client is sent to "/greetmvp/dispatch"?

    Should that be configured in the web.xml file?

    ReplyDelete
  72. Hi all,
    I've applied gwt-presenter with a server side spring support for spring-security at method level.

    Code can be found at http://code.google.com/p/orcades-gwt-spring/

    Please comment.
    The documentation is short, but fluent maven user should make it.

    ReplyDelete
  73. This comment has been removed by the author.

    ReplyDelete
  74. Chris,

    Thanks for the code, I got it to work with some minor modifications (see below).

    1) Used JDK 6
    2) Removed @override annotations as per Eclipse's recommendation in a few places.
    3) Removed the classic RPC classes such as GreetingService.java, GreetingServiceAsync.java, GreetingServiceImple.java and SendGreetingHandler.java (under sitealright package).

    Curious, have you tried to implement this using SmartGWT yet, we might see more use of this then.

    ReplyDelete
  75. Hi.

    Just wanted to thank you guys for this walkthrough. I have been using it extensively while planning my own project which I describe on http://borglin.net/gwt-project

    ReplyDelete
  76. It looks like MVP is a good idea in theory, but it clearly turns simple tasks into monolithic implementations. Look this sample MVP project here. A simple CRUD operation on a "task" becomes 16 classes. I'm sold on the idea. Not sold on the pain it takes to make this happen.

    ReplyDelete
  77. oops. forgot the link. This is a similar sample project as the one described above:

    http://code.google.com/p/gwt-mvp-sample/

    ReplyDelete
  78. Hi

    I habe been getting the following errors while compiling the client code. Can anyone help?

    Compiling module co.uk.hivedevelopment.greet.GreetMvp
    [ERROR] Errors in 'generated://D4E46AF2E7AE7489E374F22EA6E3A89C/co/uk/hivedevelopment/greet/client/gin/GreetingGinjectorImpl.java'
    [ERROR] Line 19: Rebind result 'co.uk.hivedevelopment.greet.client.mvp.GreetingPresenter.Display' must be a class
    [ERROR] Line 34: Rebind result 'co.uk.hivedevelopment.greet.client.mvp.GreetingResponsePresenter.Display' must be a class
    [ERROR] Line 91: Rebind result 'com.google.gwt.user.client.ui.HasWidgets' must be a class
    [ERROR] Line 144: Rebind result 'net.customware.gwt.presenter.client.EventBus' must be a class
    [ERROR] Line 159: Rebind result 'net.customware.gwt.dispatch.client.DispatchAsync' must be a class
    [ERROR] Cannot proceed due to previous errors

    ReplyDelete
  79. @Hang - not sure about that error, I'd have to see some code. Have you downloaded the ready-to-go eclipse project of this example?

    http://static1.hivedevelopment.co.uk/blog/gwt-best-practise-mvp-di-command/GreetMvp.zip

    Cheers,

    Chris

    ReplyDelete
  80. Thank you very much for this tutorial but i want to know why have you put the instantiation of "GreetingResponsePresenter" in the constructor of GreetingPresenter and not in the constructor of AppPresenter?

    ReplyDelete
  81. This comment has been removed by the author.

    ReplyDelete
  82. Good tutorial but this:
    @Override
    30. protected void dispatch(final GreetingSentEventHandler handler) {
    31. handler.onGreetingSent(this);
    32. }

    will not work since Overiding this method requires EventHandler rather than GreetingSentEventHandler

    Greetings from http://AngloPolish.com

    ReplyDelete
  83. hi,

    is there any way of achieving some way of code splitting here? The problem i see is in the fact that all view/presenters bindings take place in GreetingClientModule class. This will inevitable have a significant impact on the startup/download time of even medium size application. Is there any way of incorporating something like http://code.google.com/webtoolkit/articles/mvp-architecture-2.html#code_splitting

    GWT.runAsync(new RunAsyncCallback() {
    ...
    public void onSuccess() {
    ...
    }
    }

    by the way, very good article.

    ReplyDelete
  84. Hi,

    I have a question concerning the DialogBox. How the DialogBox know, that it should appear? In other words: Which event triggers the appearance of this DialogBox?

    In GreetingPresenter, you fire a GreetingSentEvent. And in GreetingResponsePresenter you add a handler for the GreetingSentEvent to the eventBus.
    But what are the steps in between? From where will be the GreetingResponsePresenter called?

    ReplyDelete
  85. @greg

    I've done code splitting with gwt-presenter. It's a bit much to write about here in the comments, but I was able specify a module:


    public class Module extends AsyncModule\
    {

    @Inject
    public Module(final Provider value)
    {
    super(value);
    }

    }


    Where UserWindowPresenter is a presenter that has a few sub presenters relating to a particular business area.

    Elsewhere, I then show the above presenter by having something like:


    public class PresenterRegistry
    {

    private Provider userWindowPresenterProvider;


    @Inject
    public void setUserManagementPresenter(final Provider provider)
    {
    userWindowPresenterProvider = provider;
    }


    private void revealUserManagementWindow()
    {
    GWT.runAsync(new RunAsyncCallback()
    {
    @Override
    public void onFailure(final Throwable reason)
    {
    }

    @Override
    public void onSuccess()
    {
    final Presenter presenter = userWindowPresenterProvider.get();

    presenter.revealDisplay();
    }
    });
    }

    }


    I then call into the presenter registry to show a form.

    I hope that helps. I'd like to find some time to write this up properly on the blog in the very near future, so watch this space.

    ReplyDelete
  86. Blogger has scrubbed generics from the code snippet I just posted. :(

    ReplyDelete
  87. @seakey

    Nothing too clever I'm afraid. GreetingResponsePresenter defines this:


    @Override
    protected void onBind() {
    ...

    eventBus.addHandler(GreetingSentEvent.TYPE, new GreetingSentEventHandler() {

    @Override
    public void onGreetingSent(final GreetingSentEvent event) {
    ...
    display.getDialogBox().show();
    }
    });
    }

    Obviously it's the call to .show() that causes the display of the dialog box. The code block is a handler for a GreetingSentEvent which is fired when a server call returns successfully:

    public class GreetingPresenter extends WidgetPresenter {
    ...

    private void doSend() {
    ...
    dispatcher.execute(new SendGreeting(display.getName().getValue()), new DisplayCallback(display) {

    ...
    @Override
    protected void handleSuccess(final SendGreetingResult result) {
    // take the result from the server and notify client interested components
    eventBus.fireEvent(new GreetingSentEvent(result.getName(), result.getMessage()));

    }

    });
    }

    ReplyDelete
  88. @Chris: Yeah, that's clear. I wanted to know, where the GreetingResponsePresenter will be triggerd. But I believe, that I have found the point: It's because of the

    bindPresenter(GreetingResponsePresenter.class, GreetingResponsePresenter.Display.class, GreetingResponseView.class);

    in GreetingClientModule, right?

    Thanks for your answer.

    ReplyDelete
  89. Hello

    Thanks for a great guide.

    I have a problem with the changing of presenters/views.

    I have a LoginPresenter/LoginView when i login i want to change the presenter/view to StartPresenter/StartView.

    Where and how should i do this?

    Thanks again

    /Filip

    ReplyDelete
  90. This comment has been removed by the author.

    ReplyDelete
  91. Hi,

    First thanks for this excellent tutorial! This incredibly helps to jump start.

    It looks like the code is no longer compatible with the 2.04 GWT environment as I get the following ERROR which breaks the app.

    Using SDK 2.0.4, Eclips (latest) and Java 1.6 on OSX

    It starts with a warning:
    14:56:04.924 [WARN] [greetmvp] Line 41: Referencing deprecated class 'com.google.gwt.user.client.rpc.SerializableException'

    [INFO] Calling doSend
    [ERROR] Handle Failure:

    14:56:30.248 [ERROR] [greetmvp] 2010-07-24 14:56:30,247 [ERROR] Handle Failure:
    com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException: Type 'co.uk.hivedevelopment.greet.shared.rpc.SendGreeting' was not assignable to 'com.google.gwt.user.client.rpc.IsSerializable' and did not have a custom field serializer. For security purposes, this type will not be deserialized.

    Now to fix this weired error, remove the gwt-sevelet.jar from the WEB-INF/lib dir and things will work again.

    Wessel

    ReplyDelete
  92. Hi,
    Do you check http://code.google.com/p/guit/ ?
    I think Guit have the same structure with this example.
    So sorry, I'm a new comer. I don't understand a lot of thing. Please help me.
    I want to use this architecture combine with GAE's datastore (using JDO)
    http://code.google.com/p/diaryoftour/
    I will learn more. Hope any body can help me :)

    ReplyDelete
  93. I'm still getting the same error with 2.0.4 and 1.5 JRE...

    I tried removing gwt-servlet.jar and it didn't work... any clues

    ReplyDelete
  94. Hi,

    Great job. Do you think that you can post an integration with Spring framework on the backend ? It seems that i can use Sping MVC or Spring4gwt (not supported cause Google Gin), Google Gin, GWT-SL, ... There is also Spring Roo (I didn't have a look at it).

    Thanks
    JMi

    ReplyDelete
  95. yea um, this article is almost too old to be worth using.
    Wish OP would submit some updates for 2010...

    I've managed to integrate gwt-presenter & dispatch, as well as work up a decent smart-gwt interface. Once I get it rock solid, I'll post it here by some means.

    I feel compelled to share it, after working an entire day getting old code from 2009 to work.
    I doubt that the above article even repesents an accurate "standards of practice" anyway. GWT has changed alot.

    Now I know why Java's logo is a coffee cup..
    it's the same as other things early in the morning that you hate to deal with, but do so anyway cause your cracked out on caffeine and lusted for money.

    ReplyDelete
  96. Jared,

    Sorry to hear that you had trouble getting this code working. I'll try to post a link to an updated snapshot based on the latest releases of the gwt-presenter/gwt-dispatch libraries. Is that the update you are referring to? If you let me know your particular pain points then I'll try to address them.

    As with many blog posts, what was written above was correct (to the best of my knowledge) at the time of writing and, indeed, a year is a relatively long time with a project evolving as rapidly as GWT. In fact GWT 2.1 will provide its own MVP library potentially removing the need for gwt-presenter. It seems harsh to call me on the fact that the code is now out of date - that's just life in the technical blogsphere.

    When I use the term "best practice", it is not self proclaimed but taken from a Google I/O 2009 talk which discusses a best practice approach to developing GWT projects which crucially means employing MVP, Command and Dependency Injection patterns. I believe this post successfully demonstrates the setup and interoperation of the required libraries to use the patterns.

    Regards,

    Chris.

    ReplyDelete
  97. Hi,

    You might like to take a look at gwt-platform (http://code.google.com/p/gwt-platform/ and http://uptick.com.au/content/getting-started-gwt-platform).

    Cheers
    Mark

    ReplyDelete
  98. Even if this is old, it still looks like a great guide to get started!

    Question: I don't see any license restrictions on any of this code. Have you released this into the public domain?

    ReplyDelete
  99. @Tom,

    Thanks! Yes the code is public domain.

    Cheers,

    Chris.

    ReplyDelete
  100. Awesome! My manager says we can put a link to here in our license header and give you props. You probably saved me a week of work.

    Would you by any chance have any recommendations on widget libraries? Some of them have some cool features we could really use, but seem to force you into the MVC pattern.

    ReplyDelete
  101. I've worked with Sencha Ext GWT (a.k.a GXT) a lot over the last 9 months or so.

    http://www.sencha.com/products/gwt/

    It's a rich library and can be adapted to fit in with MVP but it can take some work. For instance, at a basic level you'll need to make components adhere to HasValue interfaces and so on. Those sorts of changes are pretty quick. More advanced components like trees, grids and combos need more effort.

    All in all, we've got some very impressive results using this library.

    Cheers,

    C.

    ReplyDelete
  102. great explanation and example.
    Thanks a lot it helped me to learn and implement.....

    Regards,
    Dew

    ReplyDelete
  103. @Chris,

    Any progress on the solution you are working on to get rid of the fudge factor?

    Danny

    ReplyDelete
  104. HI Thanks for your tutorial. It works as miracle. Though i had a small question. How do we change the call from /greetmvp/dispatch to anything that we want like /greetmvp/checkthis and how do we call this???

    Thanks again.

    ReplyDelete
  105. Hi. Thanks for the tutorial - a great effort. I downloaded the complete project as a zip (as suggested in the article) - it compiled, but the RPC failed. The error message was "Application out of date ... expecting version 6, got 5 ...". Turned out I just had to replace gwt-servlet.jar (2009) with the latest version (2011).

    ReplyDelete
  106. Hi when I am trying to deploy the greetMVP example in my process server I am getting with Multiple Servlet injectors detected. This is a warning indicating that you have more than one GuiceFilter running in your web application. Can you help me out in this.

    ReplyDelete
  107. Thanks for this nice article. would be great if you could answer my question as well I have been in Java swing development for few years and now I see GWT getting popular , though I haven't used GWT , what are differences between GWT and Swing and how much learning curve for a swing developer to learn GWT.

    Thanks
    Javin
    How Garbage collection works in Java

    ReplyDelete
  108. This is excellent stuff man you have doe a pretty good job on explaining things. thanks a lot. set java classpath

    ReplyDelete
  109. hi have exception , please clarify this. why this happened....


    EntryPoint initialization exception
    Exception while loading module fr.micouz.gwt.dispatch.client.Gwt_dispatch_example. See Development Mode for details.

    com.google.gwt.core.ext.UnableToCompleteException: (see previous log entries)
    at com.google.gwt.dev.shell.ModuleSpace.rebindAndCreate(ModuleSpace.java:503)
    at com.google.gwt.dev.shell.ModuleSpace.onLoad(ModuleSpace.java:375)
    at com.google.gwt.dev.shell.OophmSessionHandler.loadModule(OophmSessionHandler.java:200)
    at com.google.gwt.dev.shell.BrowserChannelServer.processConnection(BrowserChannelServer.java:525)
    at com.google.gwt.dev.shell.BrowserChannelServer.run(BrowserChannelServer.java:363)
    at java.lang.Thread.run(Unknown Source)

    ReplyDelete
  110. Fist of all, your project help me to understand MVP and GWT.
    But i still do not succeed on launching the application. I have the problem:


    13:49:39.829 [ERROR] [greetmvp] Failed to create an instance of 'co.uk.hivedevelopment.greet.client.GreetMvp' via deferred binding
    java.lang.RuntimeException: Deferred binding failed for 'co.uk.hivedevelopment.greet.client.gin.GreetingGinjector' (did you forget to inherit a required module?)

    Do you have any idea of the answer to that.

    Thanks

    ReplyDelete
  111. Someone found a solution to the last question because I have the same problem.

    thanks

    ReplyDelete
  112. Thanks for the demo. This demo did one thing in particular that I couldn't find anywhere else out there- couple the MVP pattern with a DialogBox. Simple enough sounding but not so simple to see through, for me. Reason: my MVP presenter/views had all been hooked up to initialize/construct in response to events (similar to Contacts Demo) from the controller. Whereas in this case we need to instantiate the presenter/view in the bind method of the controller, so that we can have the presenter then listen for the event from the bus to then show itself. Wordy I know but I'm happy so oh well. Awesome.

    ReplyDelete