Search code examples
javagwtwidgetuibindergwt-designer

GWT Presentation Layer: Who does what?


I'm learning GWT and trying to wrap my head around all the UI options. I'm having trying to make sense of when/where/how to use Widgets, UIBinder, GWT Designer and custom Widgets. Specifically:

  • What does GWT Designer generate as output? UIBinder XML? Is it safe to say that GWT Designer can be used when you don't want to hand-code UIBinder XML, but that they both serve the same exact purpose?
  • When to use UIBinder over Widgets? Is it that Widgets get translated into UIBinder XML, but have a lot of more code (event-handling, etc.) in them? In that case, I would assume the advantage of UIBinder XML is less code and thus faster performance? Any other factors that should be considered when choosing between the two?
  • Do you write lots of UIBinder XML "snippets" or just pack them all into one big monolithic XML file?
  • For making my own custom components, what's the difference between extending com.google.gwt.user.client.ui.* vs. com.google.gwt.user.client.ui.Composite?
  • Division of labor: Widgets/UIBinder vs. Layouts vs CSS files: who does what?

To me, I feel like all of these things do the same stuff, and perhaps this is just GWT's way of providing you with multiple ways to accomplish presentation? If not, please correct me on these items.


Solution

    • I only use Widgets over UiBinder when the widget is extremely simple. For example, I'm just adding two widgets to a Panel. If there is any CSS involved, I always go with UiBinder because it is much easier to work with styles.

    • Widgets do not get translated into UiBinder XML. All GWT code becomes JavaScript that adds and manipulates DOM elements, so everything gets translated to an executable language, not to a templating system.

    • I write lots of UiBinder snippets. I try to follow good rules about abstraction and composition that you can find all over the web.

    • The MVP pattern is a must if you have any non-trivial logic because testing a GWT-free presenter is very quick and easy with JUnit, whereas GWT tests have much more overhead and are much slower.

    • I like to keep as much styling in CSS files as possible because it's a standard good practice to separate concerns, you can compress and bundle your CSS files, and lots of other reasons that are the same as why in a normal HTML page you put CSS in separate files instead of on the page directly.

    • I never use the GWT designer. I always prefer to have clean code rather than the crazy junk usually spit out by any UI code generator.

    • 99% of the time, my widgets extend Composite because either I'm using UiBinder or I'm adding things to a Panel. Even if I only have a single widget, I find it easier to extend Composite and add my one widget to a SimplePanel. I rarely extend Widget because then you have to make a call to Document.get().createFooElement() to create a DOM Element, but I typically find Widgets, which can be added to Panels, easier and safter to work with than Elements

    How I Construct Widgets

    Each widget implements an interface that extends IsWidget. Everyone who wants to use the Widget should depend on the interface, not on the underlying class. This presents a single, JSNI-free abstraction.

    If the widget is very simple, I will have a single class that extends Composite and implements the interface. Either the widget will be very simple and add a few items to a Panel or it will use UiBinder.

    If the widget has non-trivial logic that I would like to test, I use the MVP pattern. There will be a presenter class that implements the 'public' interface of the widget, a view interface that extends IsWidget that the presenter depends on, and a view widget that implements the view interface.

    A benefit of having a single 'public' interface for the widget is that you can change from implementing the interface with a single Composite class to using MVP if the logic becomes complex, and no one using the widget needs to change at all.

    I use Gin to wire all the interfaces and implementations together.

    Example

    This is best explained with some code. Let's say I have a chart that I want to use on several pages, so I decide to make a reusable widget for it. There is some non-trivial logic around processing the RPC response before displaying it, so I want to thoroughly unit test it. I'd go with something like this:

    public interface FinancialChart extends IsWidget {
      void setTickerSymbol(String tickerSymbol);
    }
    
    class FinancialChartPresenter extends Composite implements FinancialChart {
      private final FinancialChartView view;      
      private final DataServiceAsync service;
    
      @Inject(FinancialChartView view, DataServiceAsync service) {
        this.view = view;
        this.service = service;
      }
    
      @Override public Widget asWidget() {
        return view.asWidget();
      }
    
      @Override public void setTickerSymbol(String tickerSymbol) {
        service.getData(tickerSymbol, new AsyncCallback<FinancialData>() {
          @Override public void onFailure(Throwable t) {
            // handle error
          }
    
          @Override public void onSuccess(FinancialData data) {
            SimpleData simpleData = // do some parsing with presentation-specific
              // logic, e.g. make dramatic increases or decreases in price have a
              // a different color so they stand out.  End up with something simple
              // that's essentially some (x, y) points that the dumb view can plot
              // along with a label and color for each point.
            view.drawGraph(simpleData);
          }
      }
    }
    
    interface FinancialChartView extends IsWidget {
      void drawGraph(SimpleData simpleData);
    }
    
    class FinancialChartWidget extends Composite implements FinancialChartView {
      @Override public void drawGraph(SimpleData simpleData) {
        // plot the points on a chart.  set labels.  etc.
      }
    }
    
    class SomethingWithFinancialChartWidget extends Composite
        implements SomethingWithFinancialChart {
      interface Binder extends UiBinder<Widget, SomethingWithFinancialChartWidget> {}
    
      @UiField(provided = true) final FinancialChart chart;
    
      @Inject SomethingWithFinancialChartWidget(Binder binder, FinancialChart chart) {
        this.chart = chart;
        initWidget(binder.createAndBindUi(this));
      }
    }
    
    // In SomethingWithFinancialChartWidget.ui.xml
    <ui:HTMLPanel>
      <!-- lots of stuff -->
      <mynamespace:FinancialChart ui:field="chart" />
      <!-- lots more stuff -->
    </ui:HTMLPanel>
    
    class MyPackagesGinModule extends AbstractGinModule {
      @Override protected void configure() {
        bind(FinancialChart.class).to(FinancialChartPresenter.class);
        bind(FinancialChartView.class).to(FinancialChartWidget.class);
      }
    }
    

    This allows me to write very simple, thorough, and fast JUnit tests for the FinancialViewPresenter because it has no GWT dependencies that require JSNI, which has to run in a browser as part of a much slower GWT test case. You can create a mock FinancialChartView.

    One thing to note here is that since SomethingWithFinancialChartWidget is depending on the interface FinancialChart, it cannot instantiate that object because it is just an interface. That is why chart is set up as @UiField(provided = true) in the Java code of SomethingWithFinancialChartWidget. Gin set up the binding from the FinancialChart interface to a concrete class so it can provide an implementation to the @Inject constructor of SomethingWithFinancialChartWidget, and then setting this.chart gives the UiBinder the object it needs.

    There are many files that get created for all the interfaces and implementations in MVP, but the abstraction is absolutely worth it because they enable easy unit testing of presenters and allow you to change how the top-level interface, FinancialChart in this example, is implemented, e.g. change from a single Composite class to MVP, with no client needing to change.

    I'm sure there are some implementation details that may not be super clear or things I glossed over, e.g. GWT tests, so please post comments and I can edit my answer to update and clarify.