View Generation

One of the main ways gwt-mpv reduces GWT/MVP-related boilerplate is by using UiBinder ui.xml files as the single source of the view and then generating the derivative boilerplate Java code.

This frees the developer from making tedious modifications to multiple files for each UI element.

Traditional MVP

Let’s look at a simple ClientView.ui.xml file for editing clients:

<ui:UiBinder ...>
  <gwt:HTMLPanel>
    <h2 ui:field="heading">Client</h2>
    <gwt:TextBox ui:field="name"/><br/>
    <gwt:TextBox ui:field="description"/>
    <div><gwt:SubmitButton ui:field="submit" text="Submit"/></div>
  </gwt:HTMLPanel>
</ui:UiBinder>

If developing traditional MVP, you’ll have a ClientPresenter with a Display inner-interface that defines HasXxx methods:

public class ClientPresenter {
  public interface Display {
    HasText heading();

    HasValue<String> name();

    HasValue<String> description();

    HasClickHandlers submit();
  }

  // the rest of your presenter implementation
}

Which looks innocent enough. Let’s make it a little more real world by adding:

For the presenter to handle these changes, our Display interface grows to look more like:

public class ClientPresenter {
  public interface Display {
    HasText heading();

    HasValue<String> name();

    HasAllKeyHandlers nameKeys();

    HasStyle nameStyle();

    HasValue<String> description();

    HasAllKeyHandlers descriptionKeys();

    HasStyle descriptionStyle();

    HasClickHandlers submit();

    void ensureDebugId(String debugId);
  }

  // the rest of your presenter implementation
}

Okay, that looks good. Now let’s implement it:

public class ClientView implements ClientPresenter.Display {
  public static interface MyUiBinder extends UiBinder<HTMLPanel, ClientView> {
  }

  private static final MyUiBinder binder = GWT.create(MyUiBinder.class);

  private final HTMLPanel panel;

  @UiField
  protected Element heading;

  @UiField
  protected TextBox name;

  @UiField
  protected TextBox description;

  @UiField
  protected SubmitButton submit;

  public ClientView() {
    panel = binder.createAndBindUi(this);
  }

  public void ensureDebugId(String debugId) {
    UIObject.ensureDebugId(heading, debugId + "-heading");
    name.ensureDebugId(debugId + "-name");
    description.ensureDebugId(debugId + "-description");
    submit.ensureDebugId(debugId + "-submit");
  }

  public HasText heading() {
    return heading;
  }

  public HasValue<String> name() {
    return name;
  }

  public HasAllKeyHandlers nameKeys() {
    return name;
  }

  public HasStyle nameStyle() {
    return new WidgetHasStyle(name);
  }

  public HasValue<String> description() {
    return description;
  }

  public HasAllKeyHandlers descriptionKeys() {
    return description;
  }

  public HasStyle descriptionStyle() {
    return new WidgetHasStyle(description);
  }

  public HasClickHandlers submit() {
    return submit;
  }
}

This is pretty standard usage of UiBinder and nothing too complex.

But suddenly we went from 8 lines of UiBinder code to 86 lines of derivative Java code to tailor our view for MVP.

Characteristic vs. Widget Interfaces

One source of cruft is GWT’s HasXxx interfaces (typically called “characteristic interfaces” as each defines a very small, specific characteristic that may apply to several different widgets).

It is tedious to add a new Display method and new ClientView method for each HasXxx interface (HasValue, HasAllKeyHandlers, HasStyle, etc.) the presenter requires when they all come from the same source widget (see description, descriptionKeys, and descriptionStyle in the above example).

In gwt-mpv’s opinion, the HasXxx interfaces are just a band-aid around the GWT widgets not having their own, specific interfaces–a better solution is to just add these missing, widget-specific interfaces and be done with it.

This is what gwt-mpv does. For example, Anchor has IsAnchor, TextBox has IsTextBox, etc.

This also solves the very annoying problem that many GWT widget methods aren’t in HasXxx interfaces at all. So you end up having to make them up, e.g. HasStyle.

IsXxx widget interfaces are also the key to gwt-mpv’s automation because the programmer no longer has to decide which HasXxx interfaces for each widget will be exposed–each widget in the ui.xml file is just exposed in the Display interface as its IsXxx equivalent.

The presenter can now access as few or as many widget methods as it needs without changing the Display or ClientView classes.

Generated Views

Let’s go back to the ui.xml file:

<ui:UiBinder ...>
  <gwt:HTMLPanel>
    <h2 ui:field="heading">Client</h2>
    <gwt:TextBox ui:field="name"/><br/>
    <gwt:TextBox ui:field="description"/>
    <div><gwt:SubmitButton ui:field="submit" text="Submit"/></div>
  </gwt:HTMLPanel>
</ui:UiBinder>

Really, all of the key information about the view is right here.

We can see what the ui:field bindable fields are (heading, name, description, and submit), and what their types are (Element, gwt:TextBox, gwt:SubmitButton).

There is no reason a developer should have to manually copy this information over into 2 separate files (ClientPresenter.Display and ClientView). DRY.

So gwt-mpv solves this with code generation. Not GWT compile-time/deferred-binding code generation, but programmer-time, click-the-button, the-IDE-sees-the-output code generation.

By running ant or an Eclipse launch target (which Eclipse can run automatically on save), gwt-mpv will parse the client.ui.xml file and generate the interface, an implementation, and a stub for testing.

Here is the interface (generated):

public interface IsClientView extends IsWidget {
    public IsElement heading();

    public IsTextBox name();

    public IsTextBox description();

    public IsSubmitButton submit();
}

Note that all of IsXxx types are interfaces and so are fully mockable/stubable.

And the implementation (generated):

public class ClientView extends DelegateIsWidget implements IsClientView {

    private static final MyUiBinder binder = GWT.create(MyUiBinder.class);

    @UiField
    Element heading;

    @UiField(provided = true)
    final TextBox name = new org.gwtmpv.widgets.GwtTextBox();

    @UiField(provided = true)
    final TextBox description = new org.gwtmpv.widgets.GwtTextBox();

    @UiField(provided = true)
    final SubmitButton submit = new org.gwtmpv.widgets.GwtSubmitButton();

    public ClientView() {
        setWidget(binder.createAndBindUi(this));
        ensureDebugId("Client");
    }

    public void ensureDebugId(String baseDebugId) {
        UIObject.ensureDebugId(heading, baseDebugId + "-heading");
        name.ensureDebugId(baseDebugId + "-name");
        description.ensureDebugId(baseDebugId + "-description");
        submit.ensureDebugId(baseDebugId + "-submit");
    }

    public IsElement heading() {
        return new org.gwtmpv.widgets.GwtElement(heading);
    }

    public IsTextBox name() {
        return (org.gwtmpv.widgets.IsTextBox) name;
    }

    public IsTextBox description() {
        return (org.gwtmpv.widgets.IsTextBox) description;
    }

    public IsSubmitButton submit() {
        return (org.gwtmpv.widgets.IsSubmitButton) submit;
    }

    public static interface MyUiBinder extends UiBinder<HTMLPanel, ClientView> {
    }
}

And the stub (generated):

public class StubClientView extends StubWidget implements IsClientView {

    private final StubIsElement heading = new org.gwtmpv.widgets.StubIsElement();
    private final StubTextBox name = new org.gwtmpv.widgets.StubTextBox();
    private final StubTextBox description = new org.gwtmpv.widgets.StubTextBox();
    private final StubSubmitButton submit = new org.gwtmpv.widgets.StubSubmitButton();

    public StubClientView() {
        ensureDebugId("Client");
    }

    public void ensureDebugId(String baseDebugId) {
        heading.ensureDebugId(baseDebugId + "-heading");
        name.ensureDebugId(baseDebugId + "-name");
        description.ensureDebugId(baseDebugId + "-description");
        submit.ensureDebugId(baseDebugId + "-submit");
    }

    public StubIsElement heading() {
        return heading;
    }

    public StubTextBox name() {
        return name;
    }

    public StubTextBox description() {
        return description;
    }

    public StubSubmitButton submit() {
        return submit;
    }
}

So, with the stub, it’s ~90 lines of code generated from 8 lines of the client.ui.xml. An order of magnitude decrease in code a programmer has to type out.

(For more on stubs and why they are generated, see stubs and tests.)

AppViews Interface

To manage which view (XxxView or StubXxxView) should be used when your presenter needs it, gwt-mpv also generates an AppViews class with factory methods to create each of your views.

For example, gwt-hack’s AppViews class looks something like:

public class AppViews {
  public static IsAppView newAppView() {
    ...
  }

  public static IsClientView netClientView() {
    ...
  }

  public static IsClientListView newClientListView() {
    ...
  }
}

When your presenter wants to instantiate its view, it can use the appropriate AppViews static method, e.g.:

public class ClientPresenter extends AbstractPresenter<IsClientView> {
  public ClientPresenter() {
    super(newAppView());
  }
}

Presenters

After gwt-mpv generates the view, it becomes easy for the presenter to use the IsClientView interface and the assorted IsXxx widget interfaces it exposes.

Here is a ClientPresenter:

public class ClientPresenter extends AbstractPresenter<IsClientView> {

  private Client c = // get client from somewhere

  public ClientPresenter() {
    // will be either the stub or UiBinder as needed
    super(newClientView());
  }

  @Override
  protected void onBind() {
    super.onBind();

    // set the debug ids appropriately
    view.ensureDebugId("Client-" + c.getId());

    // change the heading
    view.heading().setInnerText("Client " + c.getName());

    // set values via IsTextBox.setText
    view.name().setText(c.getName());
    view.description().setText(c.getDescription());

    // listen to key ups
    view.name().addKeyUpHandler(...);
    view.description().addKeyUpHandler(...);

    // when invalid use IsWidget.addStyleName
    view.name().addStyleName("invalid");
    view.description().addStyleName("invalid");

    // when clicked use IsFocusWidget.addClickHandler
    view.submit().addClickHandler(...);
  }

Notice how:

The Result

With gwt-mpv you only maintain two files:

  1. Your ui.xml file, and
  2. Your presenter.
  3. Your presenter’s test (okay, so there are 3 files)

The tedious work of percolating ui.xml changes throughout various files just to get MVP-testable goodness is automated.

Part I vs. Part II MVP

There are some difference between the Part I and Part II MVP architectures, specifically moving from a view interface (Part I) to a presenter callback interface (Part II).

gwt-mpv currently automates a Part I-style architecture. It is not immediately clear how to automate a Part II-style architecture, which does have some nice properties, but so far as not been necessary for the applications gwt-mpv as been used on.

If anyone is interested in using a gwt-mpv-style approach for Part II architectures, feel free to bring it up on the forums.