GWT 2.8.0 ist raus

Details zu den Änderungen unter http://www.gwtproject.org/release-notes.html. Toll ist die Unterstützung von Java 8.

  • Started using ES6 Maps when available for HashMap/HashSet that yields up 3x performance improvements.
  • Partial support for Java 8 standard library APIs (see below for full list).
  • Source level set to Java 8.
  • Static and default methods in interfaces aren’t visible to generators. If you want to take advantage of those Java-8isms, you’re encouraged to switch to an annotation processor. This could break existing build if an interface is changed to turn a non-default method into a default method.
  • Emulate java.io.UncheckedIOException.
  • Emulate Optional<T> and its int, long, double variants.
  • Emulate Objects.requireNonNull() with message Supplier.
  • Fix Math.min/max(float/double) emulation behavior.
  • Emulate Character.isBmpCodePoint().
  • Emulate CharSequence.chars().
  • Emulate java.lang.SecurityException.
  • Emulate Java 8 API of
  • java.util.Arrays,
  • java.util.ArrayDeque,
  • java.math.BigInteger,
  • java.util.BitSet,
  • java.util.Comparator,
  • java.util.function,
  • java.util.Iterator,
  • java.lang.Iterable,
  • java.util.IntSummaryStatistics/LongSummaryStatistics/DoubleSummaryStatistics
  • java.util.Collection/Lists/Queues,
  • java.util.Map,
  • java.util.logging.Logger,
  • java.util.PrimitiveIterator,
  • java.util.Spliterator,
  • java.util.stream,
  • java.util.StringJoiner

gwtbootstrap3 Version 0.7 fertig

https://github.com/gwtbootstrap3 ist eine GWT-Bibliothek mit hübschen Komponenten, einen Grid-System, siehe dazu auch https://gwtbootstrap3.github.io/gwtbootstrap3-demo/, alles ist optimiert auf Umfluss für mobile Endgeräte. Es basiert auf Twitter Bootstrap http://getbootstrap.com/. Änderungen in Version 0.7:

  • Added connivence methods to easily set margins/paddings on widgets.
  • Ensured removal of all handlers on the unLoad of widgets
  • Added methods to show/hide/toggle NavbarCollapse
  • Further styled GWT’s CellTable/DataGrid to reflect Bootstrap tables
  • Upped the dependency of GWT to 2.6.0
  • Added submit/reset methods to Form
  • Renamed all setTarget/setToggle methods to setDataTarget/setDataToggle, also any setter that referenced a data* attribute was renamed
  • Added in a check as to not load jQuery if it’s already present
  • Changed the parent of gwtbootstrap3-parent to oss-parent (org.sonatype.oss)

Was ich in meinem Code ändern musste war tatsächlich die *Data* Sache:

dismissButton.setDismiss( ButtonDismiss.MODAL ); –> dismissButton.setDataDismiss( ButtonDismiss.MODAL );

dialog.setKeyboard( true ); –> dialog.setDataKeyboard( true );

anchor.setTarget( "#" + title.hashCode() ); –> anchor.setDataTarget( "#" + title.hashCode() );

anchor.setToggle( Toggle.COLLAPSE ); –> anchor.setDataToggle( Toggle.COLLAPSE );

Designfrage: Wie nutzt man GWT-RPC-Aufrufe lokal etwa bei Offline-Anwendungen?

Standardmäßig sieht es ja im “normalen” entfernen RPC-Aufruf so aus: Als erstes die Schnittstelle:

public interface ContactRpcService extends RemoteService {

  Contact getContactById( long id );

}

Dann die zugehörige Async-Schnittstelle:

public interface ContactRpcServiceAsync {

  void getContactById( long id, AsyncCallback<Contact> callback );

}

Der Client hat nun so was wie

ContactRpcServiceAsync contactService = GWT.create( ContactRpcService.class );

contactService.getContactById( contactId, new DefaultCallback<Contact>() {

  @Override protected void handleResponse( Contact response ) {

    ….

  }

} );

DefaultCallback ist eine meine abstrakte Klasse, die AsyncCallback implementiert, aber das ist jetzt nicht so wichtig.

Auch etwa anders mache ich noch, damit ich weniger schreiben muss; ich habe mir eine Klasse Rpc deklariert, mit Konstanten für alle GWT-creates():

public class Rpc {

  private Rpc() {}

  public final static ContactRpcServiceAsync  contactService = GWT.create( ContactRpcService.class );

  // … un ddie Anderen

}

Normalerweise sieht es also bei mir so aus:

Rpc.contactService.getContactById( contactId, new DefaultCallback<Contact>() {

  @Override protected void handleResponse( Contact response ) {

    …

  }

} );

Damit nun Rpc.contactService.getContactById() im Offline-Modus etwas anderes macht, kann man zum Beispiel folgendes tun: Rpc.contactService stammt nicht von GWT.create(), sondern ist ein Proxy. Falls nun der Remote-Fall gewünscht ist, delegiert man an an die echte Rpc-Implementierung, andernfalls an eine lokale Variante.

public class Rpc {

  private Rpc() {}

  public final static ContactRpcServiceAsync  contactService = new ContactRpcServiceAsync() {

    private final ContactRpcServiceAsync delegate = GWT.create( ContactRpcService.class );

    private final ContactRpcServiceAsync local = new ContactRpcServiceAsync() {
      @Override public void getContactById( long id, AsyncCallback<Contact> callback ) {

        // hier alles lokale machen, also etwas aus dem Cache holen. Wenn alles gut geht, und die Daten vorhanden sind, dann aufrufen
        callback.onSuccess( result );
      }

    };

    @Override public void getContactById( long id, AsyncCallback<Contact> callback ) {

      wenn der remote Fall

        delegate.getContactById( id, callback );

      andernfalls

        local.getContactById( id, callback );

    }

  };

}

In GWT global auf Tastenkürzel reagieren

Es setzt Strg + F den Fokus auf das Suchfeld:

Event.addNativePreviewHandler( new NativePreviewHandler() {
  @Override public void onPreviewNativeEvent( NativePreviewEvent event ) {
    NativeEvent ne = event.getNativeEvent();
    if ( event.getTypeInt() == Event.ONKEYDOWN && ne.getCtrlKey() && ne.getKeyCode() == ‚F‘) {
      ne.preventDefault();
      searchTextBox.setFocus( true );
    }
  }
} );

MVP (Model-View-Presenter) mit GWT

Zum Thema MVP (Model-View-Presenter) gibt es schon einiges an Dokumentation, etwa https://developers.google.com/web-toolkit/doc/latest/DevGuideMvpActivitiesAndPlaces, https://developers.google.com/web-toolkit/articles/mvp-architecture, doch soll dieser Beitrag einen weiteren Zugang zu dem Thema schaffen.

In einer MVP Anwendung macht die Sicht nur das, was sie machen soll: sie zeigt an. Sie ist zudem immer technologieabhängig und es kann verschiedene Implementierungen geben, etwa für GWT, Swing oder als Mock für Tests. Der Presenter ist mit der View assoziiert, aber da die View ja immer anders aussehen kann, kennt der Presenter sie nur über eine Schnittstelle. Damit beginnen wir (alle Programme im Pseudeocode):

interface MyView extends IsWidget {
  void setName( String name );
  String getName();
}

In der Schnittstelle kommen bis auf IsWidet keine GWT-spezifischen Eigenschaften nach außen, wobei es Entwickler gibt, die hier GWT-Schnittstellen wie HasText, IsWidget oder HasClickHandlers verwenden, dazu gleich mehr. Auch werden keine View-Model-Objekte (etwa Person) verwendet, sondern einfache Datentypen.

Die Implementierung für GWT kann den UiBinder nutzen oder nicht. Ohne sieht es etwa so aus:

class MyViewImpl extends Composite implements View {

  private TextBox textbox;

  MyViewImpl() {
    initWidget( textbox );
  }

  @Override public void setName( String name ) { textbox.setValue( name ); }
  @Override public String getName() { return textbox.getValue(); }
}

So einfach ist die View (die wir später noch ausbauen).

Nun kommt der Presenter, der die View initialisiert und auch die Interaktion mit dem Backend übernimmt. Wie stark er mit der View interagiert ist Geschmacksache.

Da der Presenter die View initialisiert und auf die View Einfluss nimmt, muss folglich der Presenter ein Verweis auf die View bekommen. Der Presenter merkt sich die View in einem Attribut:

class MyPresenter
{
  private MyView myView;
  …
}

Es gibt unterschiedliche Wege, wie der Presenter zur View kommt. Wenn man die View nicht austauschen möchte (und auch sonst einige Nachteile in Kauf nicht, siehe später), kann man den Presenter selbst die View erzeugen lassen. Oder man bietet einen Setter oder man lässt den Presenter injizieren. Die letzten beiden Wege erlauben den einfachen Austausch der View, etwa im Testfall. Außerdem muss die View natürlich an höher liegender Stelle irgendwie verfügbar sein, wenn zum Beispiel die MyView auf den Root-Panel gesetzt wird. Wenn man die View injiziert oder sie dem Presenter über einen Setter setzt, gibt es eine zentrale Stelle (ClientFactory in der GWT-Doku genannt) und von dort kann man diesen Teil-View für die jeweils darüber liegende View erfragen. Oder man fragt den Presenter selbst, wenn man sich so eine zentrale Stelle sparen möchte. In meinem Beispiel deklariere ich eine Methode getView() und mein Presenter erzeugt die View auch selbst:

class MyPresenter
{
  private MyView myView = new MyViewImpl();

  MyView getView() { return myView; }
}

Wenn man die View nun auf das Root-Panel setzen möchte, sieht das so aus:

MyPresenter presenter = new MyPresenter(); 
RootLayoutPanel.get().add( new ScrollPanel( presenter.getView() ) );

Einschub: Die Methode getView() kann man natürlich über eine neue Schnittstelle Presenter vorschreiben lassen, die etwa so aussehen kann:

public interface Presenter<T extends View> { 
  T getView(); 
}

In eigenen Projekten hatte ich das ursprünglich so entworfen, darin aber keinen Nutzen gefunden, und diese allgemeine Presenter-Schnittstelle wieder verworfen. Zudem hätte das auch eine View-Schnittstelle nötig gemacht, doch diese Abstraktion brachte mir nichts.

Erzeugt der Presenter die View muss man sich natürlich der Konsequenzen bewusst werden. Damit kann die View nicht mehr so einfach ausgetauscht werden (nur, wenn sie über eine Fabrik kommt), und es bringt auch den Nachteil mit, dass eine neuer Presenter-Instanz immer eine View neu erzeugt. Die View ist dann kein Singleton, die resettet und in nachfolgenden Presenter-Instanzen wiederverwendet wird. So wird jede neue Presenter-Instanz eine neue View-Instanz bilden. In meinem Beispiel soll das Ok sein, doch sollte man sich bei performanten GWT-Anwendungen im Klaren sein, die View nicht immer wieder neu zu erzeugen. Um eine zentrale ClientFactory kommt man nicht drum herum.

Im Grunde haben wir mit den drei Typen schon ein MVP realisiert:

  • MyView: Schnittstelle mit Setter/Gettern zum Verändern/Auslesen der View
  • MyViewImpl: Implementierung der View-Schnittstelle
  • MyPresenter: Kennt die View, initialisiert sind und greift auf die Daten zurück. Kennt das wahre Model

Ist die View rein passiv reichen die drei Typen aus, doch das ist nur im Ausnahmefall so. Es fehlt ein ganz entscheidender Aspekt: Was machen wir, wenn die View Ereignisse auslöst? Drückt der Benutzer einen Button, so muss eine Logik angestoßen werden. Logik ist aber Teil des Presenters, nicht der View. Zwei Realisierungen bieten sich an.

Schaut man sich https://developers.google.com/web-toolkit/articles/mvp-architecture an, so wird ein GWT-spezifischer Typ wie HasClickHandlers von der View nach außen gereicht, sodass der Presenter Logik an diesen Handler/Listener hängen kann. Hat die View einen Button, so sieht das im Code etwa so aus:

interface MyView extends IsWidget {
  void setName( String name );
  String getName();
  HasClickHandlers getOkButton();
}

Und die Implementierung:

class MyViewImpl extends Composite implements View {

  private TextBox textbox;
  private Button okButton;

  @Override public HasClickHandlers getOkButton() { return okButton; }

  …
}

Der Presenter holt sich von der View das HasClickHandlers-Objekt und hängt seine Logik daran:

class MyPresenter
{
  private MyView myView = new MyViewImpl();

  MyPresenter() {
    myView.getOkButton().addClickHandler( new ClickHandler() { … } );
  }

  MyView getView() { return myView; }
}

Den Anmeldevorgang der Listener habe ich hier in den Konstruktor gesetzt, doch ist es empfehlenswert, sie in eine eigene (private) Methode bind() auszulagern.

Dieser Weg ist einfach und kostet verhältnismäßig wenig Code. Allerdings muss man das auch kritisch sehen, denn HasClickHandlers ist eine GWT-Schnittelle, genauso wie com.google.gwt.event.dom.client.ClickHandler. Möchte man eine View total von der Technologie unabhängig machen, so stören diese Typen, wobei es sehr angenehm ist, dass es Schnittstellen sind, und so auch von Swing/SWT/JSF im Prinzip umgesetzt werden können. Eine zweite Sache ist, dass man sich überlegen muss, wo man die Grenze bei den Typen zieht. Ein Textfeld zum Beispiel implementiert im Prinzip HasValue<String>, man kann also so weit gehen, auf View-Schnittstellen-Methoden wie

setValue(String)

String getValue()

zu verzichten und stattdessen so etwas wie

HasValue getValue()

zurückzugeben weil darüber ja ein setValue()/getValue() möglich ist.

Die neuen GWT-Beispiele etwa von https://developers.google.com/web-toolkit/doc/latest/DevGuideMvpActivitiesAndPlaces gehen einen anderen Weg und lassen die View selbst die Listener anhängen. Die View darf aber natürlich immer noch nicht die Logik ausführen, weshalb die View auf Methoden vom Presenter zugreifen kann. Damit gibt es eine bidirektionale Beziehung. Es wird mehr Arbeit als bei einer Lösung wie HasClickHandlers und das, was der View auf dem Presenter aufrufen möchte muss in einen neuen Typ fließen, denn die View soll ja nicht MyPresenter bekommen, sondern einen Basistyp.

Die Beschreibung der Presenter-Methoden kommt als innere Schnittstelle in die View-Schnittstelle und muss einen Presenter annehmen können:

interface MyView {

  void setName( String name );
  String getName();

  void setPresenter( Presenter p );

  interface Presenter {
    ok();
  }
}

Der Button selbst kommt nun nicht mehr nach außen, auf HasClickHandlers getOkButton() können wir verzichten.

Die View muss jetzt setPresenter() implementieren:

class MyViewImpl extends Composite implements View {

  private TextBox textbox;
  private Button okButton;
  private Presenter presenter;

  @Override public setPresenter( Presenter p ) { presenter = p; }
  …
}

Mit dem Verweis auf den Presenter bekommt die View Zugriff auf die Logik von ok(), die immer dann aufgerufen werden soll, wenn der Button gedrückt wird. Die View wird ergänzt um die Ereignisbehandlung:

  MyViewImpl() {
    okButton.addClickHandler( new ClickHandler() {
      presenter.ok();
    } );
  }

Das war es mit der View. Noch netter ist es natürlich, wenn man den UiBinder einsetzt, denn dann wird die Anmeldung noch etwas simpler:

@UiHandler("okButton") void onOkClick( ClickEvent e ) {
  presenter.ok();
}

Der Presenter muss nur noch ok() implementieren, hat aber mit der Anhängen eines Listeneres nichts mehr zu tun:

class MyPresenter implements MyView.Presenter
{
  private MyView myView = new MyViewImpl();

  MyView getView() { return myView; }

  @Override public void ok() { … };
}

Bewertung

Zielt man MVP voll durch, ersteht viel Code. In meinem Projekt hat eine View-Schnittstelle über 100 Methoden, die implementierende Klasse ist voll von kleinen Settern und Gettern. Schön ist das nicht. Wirklich vereinfachen kann man das nur dann, wenn man a) auf diese kleine Mini-Presenter-Schnittstelle in der View verzichtet und somit dem Presenter erlaubt, direkt die Listener anzumelden, und b) wenn man sich von dem schnittstellenorientierten Ansatz verabschiedet:

  • Statt einer Schnittstelle MyView und einer Implementierung MyViewImpl schreibt man nur die eine konkrete View-Klasse und referenziert diese im Presenter direkt. Aus “private MyView myView;” wird also “private MyViewImpl myView;”. Setter/Getter können bleiben.
  • Verschärfte Variante: Man verzichtet in der View auf die vielen Setter/Getter und greift in Presenter direkt auf die GWT-Widget zurück. Dann am Besten über die Schnittstellen um sich relativ unabhängig von den GWT-Klassentypen zu machen.

Wie weit man geht, ist jedem selbst überlassen.

Nützliches GWT-Wissen

  1. GWT.isScript() liefert false, wenn die Anwendung im Entwicklungsmodus läuft. isScript() liefert dann true, wenn die GWT-Anwendung in JavaScript übersetzt wurde. Die Funktion ist nützlich, wenn in der der lokalen Umgebung Dinge anders sind (etwa Pfade), als in der übersetzten Produktivversion.
  2. GWT.getModuleBaseURL() liefert die URL zum Wurzelverzeichnis der GWT-Applikation. Siehe dazu auch http://code.google.com/support/bin/answer.py?answer=60560&topic=10211.
  3. GWT.setUncaughtExceptionHandler() setzt einen Handler, der immer dann aufgerufen wird, wenn die Applikation eine RuntimeException sieht. Debuggen kann man etwa mit http://www.asquare.net/gwttk/apps/demo/Demo.html#debug.
  4. GWT 1.6 bringt einen neuen Eventing-Mechanismus mit (erinnert an EventBus). http://www.itsolut.com/chrismusings/2009/04/28/business-events-with-gwt-16/ stellt ihn vor.
  5. Neben dem Paket server und client gibt es das Paket public (etwa src/com/example/cal/public/), in das alles kopiert wird, das die Client als statische Ressourcen verwenden möchte. Es kommt zu den anderen compilierten Dokumenten. Siehe http://code.google.com/webtoolkit/doc/1.6/DevGuideOrganizingProjects.html#DevGuideDirectoriesPackageConventions.
  6. Eine eigene CSS-Datei für das Styling (etwa lala.css) setzt man zunächst in den in den public-Ordner. In der Bla.gwt.xml bei den <inherits> fügt man dann die Zeile <stylesheet src="lala.css"/> hinzu.
  7. panel.setHorizontalAlignment() setzt nicht den Panel selbst nach rechts/links, sondern alle Elemente, die folgen.
  8. Soll in einer Zeile ein Element rechts, das andere Links angeordnet sein, so kann man ein HorizontalPanel aufbauen, das auf 100% setzen, zwei Elemente rechts und links setzen und dann die Zellen jeweils mit setCellHorizontalAlignment(w1, HorizontalPanel.ALIGN_LEFT) und setCellHorizontalAlignment(w2, HorizontalPanel.ALIGN_RIGHT) ausrichten.

Mein Umstieg auf UiBinder (GWT)

Das komplette Frontend für die tutego-Kunden/Trainer/Serminarverwaltung ist in GWT implementiert. Die Codebase ist auf dem Level von GWT 1.1 und bis zur letzten Version hat sich viel geändert. Über Updates blogge ich regelmäßig. Es wurde Zeit neu zu betrachten, welche Teile meiner Software ich vielleicht umbauen kann. Viele Themen sind spannend, aber leider gibt im Netz kaum Dokus, und da meine Lösung läuft, war die Motivation nicht sonderlich hoch, Stunden zum Rumspielen zu investieren.

Eine der großen Neuerungen in GWT 2.0 ist die Möglichkeit, deklarativ Oberflächen aufzubauen. Im Mittelpunkt steht das UiBinder-Famework, was eine XML-Dateien zur Beschreibung eines Widget-Baum nutzt und in eine initialisierte GWT-Komponente überführt. Das Feature ist relativ gut dokumentiert und die Änderungen im Code sind klein. Daher habe ich mir ein paar Stunden für die Überführung einer View genommen.

Für jede View gibt es bei mir eine Klasse, die von einer GWT-Klasse wie Composite abgeleitet ist. Sie wird vom Presenter aufgebaut. Die View-Klasse deklariert eine Reihe von Objektvariablen. Die werden später initialisiert. Vorher sahen meine Programme etwas so aus:

// Contact person

HTML contactPersonLabel = new HTML( "<b>AnsprechpartnerIn</b> (Name, EMail, Telefon)" );
contactPersonLabel.getElement().getStyle().setMarginTop( 0.5, Unit.EM );
panel.add( contactPersonLabel );
panel.add( contactPersonTextBox = new FormTextBox() );   

// tutego rate per day

HTML ratePerDayLabel = new HTML( "<b>tutego Tagessatz</b> (netto)" );
ratePerDayLabel.getElement().getStyle().setMarginTop( 0.5, Unit.EM );
panel.add( ratePerDayLabel );
panel.add( new HorizontalFlowPanel( ratePerDayTextField = new FormIntegerTextBox(), new HTML( " Euro" ) ) );
ratePerDayTextField.setWidth( "5em" );

Mit Styles arbeite ich auch, wobei ich dies nicht für das Mikrolayout verwende. Lokale Abstände initialisierte ich immer direkt.

Der UiBinder externalisiert die Beschreibung des Objektgraphen in eine XML-Datei. Es ist wichtig zu verstehen, dass der UiBinder keinen Template-Engine ist, also die XML-Datei kein XHTML ist, wo man a) einfach HTML schreiben kann und b) so etwas wie Wiederholungen/Fallunterscheidungen schreiben kann. Es ist “nur” eine Beschreibung des Objektgraphen und ausschließlich GWT-Widgets werden dort referenziert. (Auch wenn das bei einem SpanElement mit <div></div> auf den ersten Blick nicht so aussieht.)

Das GWT-Plugin für Eclipse ermöglicht gleich das Anlegen der XML-Datei und der Klasse, was für neue Views praktisch ist. Ich habe von Hand eine XML-Datei EditCustomerView.ui.xml aufgebaut, die so aussieht:

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui=’urn:ui:com.google.gwt.uibinder‘
    xmlns:g=’urn:import:com.google.gwt.user.client.ui‘ xmlns:t=’urn:import:traida.client.web.util‘>
    <ui:with field=’res‘ type=’traida.client.resource.TraidaResources‘ />
    <ui:style>
       .bottomGap { margin-bottom: 1em; }
    </ui:style>
    <g:VerticalPanel>
        <g:Label styleName='{res.style.header1}‘ ui:field=’header‘ />

        <t:TitledPanel>
            <g:HTML>Adresse und Kontakt</g:HTML>
            <g:Anchor ui:field=’editContactAnchor‘>[Bearbeiten]</g:Anchor>
        </t:TitledPanel>
        <g:HTML styleName='{style.bottomGap}‘ ui:field=’businessCardHTML‘ />

        <t:TitledPanel>
            <g:HTML>Kurs(e)</g:HTML>
            <g:Anchor ui:field=’addNewTrainingAnchor‘>[+]</g:Anchor>
        </t:TitledPanel>
        <t:VerticalFlowPanel styleName='{style.bottomGap}‘ ui:field=’trainingsPanel‘ />

        <t:TitledPanel>
            <g:HTML>Vermerk und Logo</g:HTML>
        </t:TitledPanel>

    </g:VerticalPanel>

</ui:UiBinder>

Am Anfang werden zwei Namensräume g und t aufgebaut, einmal für die Google-Komponenten, einmal für eigene. Eigene Komponenten können einfach verwendet werden, auch die Tastaturvervollständigung klappt, ist allerdings (nur bei mir?) ziemlich träge.

Wichtig sind die ui:field-Elemente. Sie entsprechen Variablen, die in einer Java-Klasse deklariert werden. Parallel zu der XML-Datei EditCustomerView.ui.xml gibt es eine andere zugehörige Java-Klasse EditCustomerView. Sie beginnt so:

class EditCustomerView extends Composite
{
  interface EditCustomerViewUiBinder extends UiBinder<Widget, EditCustomerView> {}  // 2
  private static EditCustomerViewUiBinder uiBinder = GWT.create(EditCustomerViewUiBinder.class);

  @UiField Label header;    // 1

  …

  EditCustomerView()
  {
    initWidget( uiBinder.createAndBindUi( this ) );  // 3
    setWidth( "800" ); 
     …

  }

}

Im Rumpf ist etwas Magie, und drei Details sind wichtig:

  1. Die Klasse hat für alle in der XML-Datei deklarierten ui:field-Dinge eine Objektvariable mit der entsprechenden Annotation @UiField. Die Typen müssen zusammenpassen, also Label zu <g:Label> usw. Der UiBinder instanziiert selbständig die Typen durch den Standardkonstruktor. Ist keiner vorhanden, muss man tricksen und eine Factory einführen.
  2. Zu den Generics beim UiBinder: das erste Typargument (hier Widget) ist das, was später als Ergebnis “rauskommt”. In der XML-Datei nutze ich ein VerticalPanel, sodass der Typ hätte eigentlich auch spezieller sein können, doch den spezielleren Typ brauche ich nicht. Das zweite Typargument ist die Klasse, die die @UiField-Variablen deklariert, also die eigene Klasse selbst.
  3. Der Aufruf createAndBindUi() baut den Objektbaum auf. Das Ergebnis der Methode ist vom Typ, der im ersten Generics zugewiesen wurde, also im Beispiel Widget.

Das zur Technik. Die Frage ist, ob sich die Umstellung gelohnt hat. Bisher habe ich eine View umgestellt, doch das Ergebnis sieht gut aus. Die hierarchische Gliederung ist übersichtlich, wobei mir die XML-Ansicht reicht und ich den Designer nicht brauche. Allerdings fände ich es besser, das Layout einfacher verändern zu können. Soll zum Beispiel der Abstand zur nächsten Zeile erholt werden, so kann man nicht einfach beim Element selbst ein CSS-Syle setzen, sondern muss ein <style> Element einführen, ihm einen Namen geben und dann zuweisen. Das ist mehr Schreibarbeit als nötig. Die anderen Views werde ich nun auch umstellen.

Ob ich die UiBinder für jede View einsetzen werde wird sich zeigen, da einige Views viel stäter aus HTML aufgebaut werden und die View im Prinzip nur einen Titel setzt und der Rest ist HTML.

Ein paar Links:

GWT 2.1.1

http://googlewebtoolkit.blogspot.com/2010/12/gwt-211-is-now-available.html listet auf:

GWT SDK

GWT’s RequestFactory component, introduced in GWT 2.1, received a lot of attention, both from the GWT team at Google and from the GWT open source community at large. Based on this feedback, we’ve added the following:

  • A service layer, which includes support for non-static service objects
  • Value object support
  • Multiple methods calls on a single request

Google Plugin for Eclipse

  • Improved UiBinder error reporting within SpringSource Tool Suite (STS)
  • Optimized the IDE experience by removing unused Java builders and leveraging the AspectJ fixes in the latest STS release
  • Updated Speed Tracer to perform a full J2EE publish before launching

GWT Designer