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.xml
forui:field
declarations, record their type+name - For each
ui:field
, come up with itsHasXxx
(or reallyIsXxx
, see later) - Create
IFooView
with a methodHasXxx
for eachui:field
- Create
FooView
with a@UiField
-annotated field + method for eachui:field
- Create
StubFooView
with 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 inGwtAnchor
that just callanchor.xxx
. - Inheritance: make a new class
GwtAnchor extends Anchor implements IsAnchor
and your mostly done, though now yourui.xml
file will have to have afoo:GwtAnchor
declaration instead of the regulargwt:Anchor
. - Forking: Add
IsAnchor
to the GWT code base and just make the realAnchor
implementIsAnchor
. 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.