This will be brief, but lately I’ve been on a crusade to reduce the boilerplate in GWT MVP projects.
My current target has been the trio of FooView.ui.xml, FooPresenter.Display, and FooView, all of which are intrinsically linked. You end up making changes in one (usually FooView.ui.xml) and then dutifully percolating those changes to the other two.
Or other three, as in my case I throw in a StubFooView for testing (I find, after two or three uses, mocks get old and stubs are more pragmatic).
So, I did a short spike for the last two mornings and am becoming convinced I can generate everything from the single FooView.ui.xml file.
The steps are basically:
- Scan
FooView.ui.xmlforui:fielddeclarations, record their type+name - For each
ui:field, come up with itsHasXxx(or reallyIsXxx, see later) - Create
IFooViewwith a methodHasXxxfor eachui:field - Create
FooViewwith a@UiField-annotated field + method for eachui:field - Create
StubFooViewwith the stub version of eachui:field
I have this all basically working. It means for each presenter/view in your project, instead of 4+ hand-written files (presenter, ui.xml, interface, view, stub), you have just 2 (presenter, ui.xml).
For me, this would make GWT MVP drastically less painful.
HasXxx vs. IsXxx
Mapping a ui:field to a single HasXxx interface is problematic because each widget has multiple HasXxx interfaces. For example, Anchor has HasClickHandlers, HasAllFocusHandlers, HasName, HasHTML, etc.
If you put a gwt:Anchor in your ui.xml, which HasXxx should we expose to the presenter? What if the presenter needs more than one?
My answer is to introduce a new interface, IsAnchor, that extends all of these interfaces and, by returning just the one IsAnchor type, your presenter has access to all of its constituent HasClickHandlers/etc. methods.
To me this makes a lot of sense anyway, as I was always annoyed with having to return the same Anchor field multiple times in my view implementation, once for each HasXxx interface I needed for it.
The Problem
Turns out Anchor doesn’t actually implement IsAnchor. Of course, given IsAnchor is our own interface.
So, we have several options:
- Composition: make a new class
GwtAnchor implements IsAnchor, with a constructorGwtAnchor(Anchor anchor), then you implement a whole bunch of delegate methods inGwtAnchorthat just callanchor.xxx. - Inheritance: make a new class
GwtAnchor extends Anchor implements IsAnchorand your mostly done, though now yourui.xmlfile will have to have afoo:GwtAnchordeclaration instead of the regulargwt:Anchor. - Forking: Add
IsAnchorto the GWT code base and just make the realAnchorimplementIsAnchor. Done.
I started out with composition only because I’ve only done a few minor widgets. But it involves a whole lot of boilerplate. Inheritance is probably what I should try next. But forking is awfully tempting because it, for the most part, just makes the problem go away.
Of course, even with forking, I’ll still have to implement a StubIsAnchor for testing. Hopefully that won’t be too bad. It is definitely an up-front investment, but I’m convinced the stub will pay off over the long term vs. mocking.
My ViewGenerator spike is currently 200 lines. Pretty simple really, it will not get much larger. The hard part is surgically inserting the IsXxx interfaces into the multitude of GWT widgets. Which is not so much hard, but potentially very time consuming to get full coverage of all of the widget.