Thursday, July 22, 2010

GXT MVC Code Splitting on GAE (AppEngine)

This is an example of how I used GWT code splitting with GXT and ran it on Google AppEngine.

There seems to be some misunderstanding that controllers need be registered in advance which draws in all the code and disallows code splitting. This is not the case and controllers can easily be registered when and only when requested.

Code is available via GIT and the simple demo deployed on appengine.

This is just a simple rework for the GXT mail example.

First, we need the controllers we will use later.

TaskController taskController;
ContactController contactController;

Then, we add a listener to initialize them the first time they are used.

public void onModuleLoad() {
....

final Dispatcher dispatcher = Dispatcher.get();
dispatcher.addDispatcherListener(new DispatcherListener() {

@Override
public void beforeDispatch(final MvcEvent mvce) {

switch (CONTROLLERS.getControllerByEvent(mvce.getAppEvent().getType())) {


CONTROLLERS is just an enum set to return the controller for any event. Since there may be more than one event per controller, you could conceivably end up with something like the following which would work in a switch statement to do what you want.

public enum CONTROLLERS {
APP(AppEvents.Login),
APP2(AppEvents.Error),
APP3(AppEvents.Init),
//MAIL(AppEvents.Init),
MAIL2(AppEvents.NavMail),
MAIL3(AppEvents.ViewMailItem),
MAIL4(AppEvents.ViewMailItems),
MAIL5(AppEvents.ViewMailFolders),
TASK(AppEvents.NavTasks),
CONTACT(AppEvents.NavContacts);

In real apps I use GWT's History Mechanism
and another enum that lets me look up both the AppEvents and also the appropriate controller. But that enum-fu is best left out of this simple example.
Anyway, if the controller is null when called, we use GWT.runAsync to fetch it:

switch (CONTROLLERS.getControllerByEvent(mvce.getAppEvent().getType())) {
case CONTACT:

if (contactController == null) {
mvce.setCancelled(true);

GWT.runAsync(new RunAsyncCallback() {

@Override
public void onFailure(Throwable reason) {
//log
}

@Override
public void onSuccess() {
contactController = new ContactController();
dispatcher.addController(contactController);
GWT.log("fetched and added contactController");
Dispatcher.forwardEvent(mvce.getAppEvent().getType(), mvce.getAppEvent().getData());
}

});

}
break;

... etc. ect. for the other controllers

...For AppEngine, the trick was cancelling the dispatch and then re-calling it after the code was fetched. It worked on the development server in eclipse without recalling the event, but failed silently when deployed.


if (contactController == null) {
mvce.setCancelled(true);

...
Dispatcher.forwardEvent(mvce.getAppEvent().getType(), mvce.getAppEvent().getData());


I can see the deferred code being fetched in firebug and if I run the reports on a real project when compiling GWT, can see a difference in the initial size of the download. There are portions of my app that many users will never use, so it makes sense to do it this way.

Right now for an app .6 MB, .4 is initial and .2 is deferred (and not likely to be used by the majority of users). Keep in mind gxt2.x was designed before runAsync came out and from their mailing lists seems targeted to be redone for 3.0 (well, if "nothing can be done before v3" means targeted -- I don't really know how it'll change).

No comments:

Post a Comment

Followers