Classic MVP
You know how to post a link in facebook? - Recently I had to create a this functionality for a little GWT travelling app.
So you can enter a URL, which is then fetched and parsed. You can select one of the images from the page, review the text and finally store the link.
Now how to properly set this up in MVP? - First, you create an abstract interface resembling the view:
interface Display { HasValue<String> getUrl(); void showResult(); HasValue<String> getName(); HasClickHandlers getPrevImage(); HasClickHandlers getNextImage(); void setImageUrl(String url); HasHTML getText(); HasClickHandlers getSave(); }It makes use of interfaces GWT components implement that give some access to their state and functionality. During tests you can easily implement this interface without referring to GWT internals. Also, view implementation may be changed without influence on deeper logic.
The implementation is straightforward, shown here with declarated UI fields:
class LinkView implements Display @UiField TextBox url; @UiField Label name; @UiField VerticalPanel result; @UiField Anchor prevImage; @UiField Anchor nextImage; @UiField Image image; @UiField HTML text; @UiField Button save; public HasValue<String> getUrl() { return url; } public void showResult() { result.setVisible(true); } // ... and so on ... }The presenter then accesses the view using the interface, which by convention is written inside the presenter class:
class LinkPresenter interface Display {...}; public LinkPresenter(final Display display) { display.getUrl().addValueChangeHandler(new ValueChangeHandler<String>() { @Override public void onValueChange(ValueChangeEvent<String> event) { Page page = parseLink(display.getUrl().getValue()); display.getName().setValue(page.getTitle()); // ... display.showResult(); } }); } // ... and so on ... }
So here we are: Using MVP, you can structure your code very well and make it easily readable.
The simplification
The payoff is: Three types for each screen or component. Three files to change whenever the UI is re-defined. Not counted the ui.xml file for the view declaration. For a lazy man like me, these are too many. And if you take a look at the view implementation, it is obvious how to simplify this:
Use the view declaration (*.ui.xml) as the view and inject ui elements directly into the presenter:
class LinkPresenter @UiField HasValue<String> url; @UiField HasValue<String> name; @UiField VerticalPanel result; @UiField HasClickHandlers prevImage; @UiField HasClickHandlers nextImage; @UiField HasUrl image; @UiField HasHTML text; @UiField HasClickHandlers save; public LinkPresenter(final Display display) { url.addValueChangeHandler(new ValueChangeHandler<String>() { @Override public void onValueChange(ValueChangeEvent<String> event) { Page page = parseLink(url.getValue()); name.setValue(page.getTitle()); // ... result.setVisible(true); } }); } // ... and so on ... }Since it is possible to declare the injected elements using their interfaces this presenter has a lot of the advantages of the full-fledged MVP presenter: You can test it by setting implementing components (see below) and you can change the views implementation easily.
But now, you have it all in one class and one view.ui.xml-file and you can apply structural changes much simpler.
Making UI elements abstract
TextBox implements HasValue<String>. This is simple. But what about properties of ui elements that are not accessible through interfaces? An example you may already have recognized is the VerticalPanel named result in the above code and its method setVisible(), which unfortunately is implemented in the UiObject base class. So no interface is available that could eg. be implemented at test time. For the sake of being able to switch view implementations, it would be better to inject a ComplexPanel, but even that cannot be instantiated at test time.
The only way out in this case is to create a new Interface, say
interface Visible { void setVisible(boolean visible); boolean isVisible(); }and subclass interesting UI components, implementing the relevant interfaces:
package de.joergviola.gwt.tools; class VisibleVerticalPanel extends VerticalPanel implements Visible {}This seems to be tedious and sub-optimal. Nonetheless, is has to be done only per component and not per view as in the full-fledged MVP described above.
Wait - how to use self-made components in UiBuilder templates? - That is simple:
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:t="urn:import:de.joergviola.gwt.tools"> <g:VerticalPanel width="100%"> <g:TextBox styleName="big" ui:field="url" width="90%"/> <t:VisibleVerticalPanel ui:field="result" visible="false" width="100%"> </t:VisibleVerticalPanel> </g:VerticalPanel> </ui:UiBinder>
Declaring handlers
The standard way of declaring (click-)handlers is very convinient:
@UiHandler("login") public void login(ClickEvent event) { srv.login(username.getValue(), password.getValue()); }In the simplified MVP approach, this code would reside in the presenter. But the ClickEvent parameter is a View component and can eg. not be instantiated at runtime. On the other hand, it cannot be eliminated from the signature because UiBuilder requires an Event parameter.
So unfortunately one has to stick back to registering ClickHandlers manually (as one has to do in full MVP anyway):
public initWidget() { ... login.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { login(); } }); ... } public void login(ClickEvent event) { srv.login(username.getValue(), password.getValue()); }
Testing
Making your app testable is one of the main goals when introducing MVP.GwtTestCase is able to execute tests in the container environment but requires some startup-time. In TDD, it is desirable to have very fast-running tests that can be applied after every single change without loosing context.
So MVP is designed to be able to test all your code in a standard JVM. In standard MVP, you create implementations of the view interfaces. In this simplified approach, it is sufficient to create implementations on a component interface level like the following:
class Value<T> implements HasValue<T> { private T value; List<ValueChangeHandler<T>> handlers = new ArrayList<ValueChangeHandler<T>>(); @Override public HandlerRegistration addValueChangeHandler( ValueChangeHandler<T> handler) { handlers.add(handler); return null; } @Override public void fireEvent(GwtEvent<?> event) { for (ValueChangeHandler<T> handler : handlers) { handler.onValueChange((ValueChangeEvent) event); } } @Override public T getValue() { return value; } @Override public void setValue(T value) { this.value = value; } @Override public void setValue(T value, boolean fireEvents) { if (fireEvents) ValueChangeEvent.fire(this, value); setValue(value); } }As usual, you have to inject this component into the presenter-under-test. Though in principle, you could create a setter for the component, I stick to the usual trick to make the component package-protected, put the test into the same package (but of course different project folder) as the presenter and set the component directly.
What do you win?
You get code structered as clean as in full MVP with much less classes and boilerplate code.
Some situations require utility classes for components and their interfaces, but as time goes by, you build an environment which is really easy to understand, test and extend.
I'm curios: Tell me your experiences!
Hi, I am glad you brought that up.
You said three artifacts for each screen or component, in full GWT MVP, but it is really worse than that! If we follow the official documentation then for each view we need at-least 5 artifacts:
MyView (view interface)
MyViewImpl (view implementation)
MyViewImpl.ui.xml (UIBinder xml)
MyViewPresener (activity interface)
MyViewPresenerImpl (activity implementation)
Now, if we add the necessary Places and Tokenizers we reach 7 artifacts for each and every view.
Personally I gave up. I now unit test only server side code. That eliminates the need to have interfaces for views and presenters.
You're right. If you count Template and Presenter interfaces, Activities and Places, you have even more files to maintain.
I have to look into Activities and Places, not sure how that could be simplified.
In my reality, server side always is near to trivial. Client side testing is the interesting thing.
We used GWT 2.0 and MVP in our last project without UiBinder (because I do not like it - but I do not want to start a discussion about UiBinder here).
The documentation of Google was very good. We realized MVP by ourselves. Same for simple History Management with history tokens. It worked and was fine. Nevertheless, there is some boilerplate code, of course.
I do not like the new approaches (integrated MVP, Activities and Places, using Gin for DI). They increase the complexity a lot in my opinion.
If you need these features, use them! But in the GWT projects I have seen (to be fair, only three of medium size), these features increase complexity without returning any additional benefits.
What is your opinion about these new approaches versus the oldstyle solutions (UiBinder excluded, I know advantages and disadvantages of it).
Best regards,
Kai Wähner (Twitter: @KaiWaehner)
Thanks for your opinion. Funny - i have seen exactly these (MVP, activities, places and gin) in one project recently. My impression was not really increased complexity, given that you had to manage history with some sort of tokenizer before manually.
gin is special. I must admit that I never quite understood the earnings of DI given the amount of complexity it introduces.
(PS: I cannot suppress at least three words: I love UiBuilder ;-)
I replied over on the syndicated Java Code Geeks blog:
http://www.javacodegeeks.com/2012/02/gwt-mvp-made-simple.html
But, basically, I built Tessell to address all of your concerns.
It generates view interfaces and view implementations from the ui.xml file, so you maintain only the ui.xml file and the presenter (and the test).
If has a pre-activities places mechanism (@GenPlace) that generates places that just call a static "load" method in your existing presenter.
It has widget interfaces (IsTextBox) that unify all of a widgets characteristic interfaces (HasValue/etc.) plus has the widget's unique methods that aren't otherwise available.
It has stub implementations (like your Value for HasValue) but for all (most) of the widgets, e.g. StubTextBox for TextBox.
To me, I went through all of the pain you describe here, and Tessell is what made MVP fun again. Check it out and let me know what you think!
http://www.tessell.org