In an attempt to orient myself and my colleagues with GWT, I've been reading up on GWT, trying things out, and blogging about them here. My goal is to start with simple use cases and build on to them with more complex ones. The way it's been working so far is that each post builds on the prior one.

Wednesday, February 17, 2010

AppController and Event Handling

In the absence of any other object in our ContactList application, we’d send a Contact to the ContactDetailPresenter when a Contact was selected in the ContactListPresenter.

And if the user updates Joe’s first name to Joseph, we’d send a message back to the Contact List so that it displays Joseph instead of Joe.

image

And then Product Management might come along and say that the Browser Window title should reflect the selected Contact. In response, we’d add a BrowserWindowPresenter and wire things up to get that displaying what it should.

image

Everything in theory will work, but we are getting to a place where the UI components’ presenters are intertwined and dependent upon each other. ContactDetailPresenter needs a handle to ContactListPresenter and BrowserWindowPresenter in order to send them contact-updated notifications. ContactListPresenter needs a handle to ContactDetailPresenter and BrowserWindowPresenter in order to send them contact-selected notifications. It’s not too hard to imagine that, as a page gets more complex, the number of dependencies among the Presenters will grow. This means a couple things:

· This makes testing challenging. In order to test the ContactListPresenter behavior in isolation, we would need to mock the BrowserWindowPresenter and the ContactDetailPresenter.

· If we like the Contact Detail UI Component and decide it should be re-used on a different page, we’d have to do significant refactoring to decouple the Contact Detail UI Component from the Contact List UI Component and the Browser Window UI Component.

To address these dependency issues, we’ll introduce an AppController to coordinate messages between UI Components and we’ll build an Event Bus in to each presenter so that the AppController can register interest in the presenter events and can react accordingly.

With the AppController in place, the diagram now looks like so:

image

The event bus in each Presenter is how the AppController registers interest in event. Then when one presenter raises events, the AppController is notified so that it can instruct other presenter what to do. We’ve now addressed the two dependency issues raised above. We can test any presenter in isolation and we can use the Contact Details UI component in any page.

GWT Events: GWT has a built-in mechanism to do event-handling. GWT’s HandlerManager class can serve as an event bus. Presenters (or whoever) can register interest in particular events using the addHandler(EventHandler) method. After that, GwtEvents can be fired on to the HandlerManager using the fireEvent(Event) method, in which case the interested parties will be notified. GWT Events have 3 main components: a Type, an Event Definition, and an Event Handler. By use of Java generics, the Event Definition is tied to a particular Type and EventHandler. This ultimately prevents us from making mistakes like handling a particular event with the wrong event-handler.

We’ll now take a couple of the events discussed for the Contact Management application and see how the code would look using GWT. First, we’ll define the needed events, ContactUpdatedEvent and ContactSelectedEvent.

Given this, here are what the ContactUpdatedEvent and ContactSelectedEvent events could look like. The ContactUpdatedEvent will be constructed with a particular Contact and can dispatch to any ContactUpdatedEventHandler.


public class ContactUpdatedEvent extends
GwtEvent{
public Contact contact;
public ContactUpdatedEvent(Contact contact){
this.contact = contact;
}
public static final Type TYPE = new Type();
public static interface ContactUpdatedEventHandler extends EventHandler{
public void handleEvent(ContactUpdatedEvent contact);
}
@Override
protected void dispatch(ContactUpdatedEventHandler handler) {
handler.handleEvent(this);
}
@Override
public Type getAssociatedType() {
return TYPE;
}
public Contact getUpdatedContact(){
return contact;
}
}

Note: GwtEvent is a class delivered in the core GWT library. EventHandler and Type are interfaces delivered in the core GWT library.

And similarly, the ContactSelectedEvent will be constructed with a particular Contact and can dispatch to any ContactSelectedEventHandler.



public class ContactSelectedEvent extends
GwtEvent{
public Contact contact;
public ContactUpdatedEvent(Contact contact){
this.contact = contact;
}
public static final Type TYPE = new Type();
public static interface ContactUpdatedEventHandler extends EventHandler{
public void handleEvent(ContactUpdatedEvent contact);
}
@Override
protected void dispatch(ContactUpdatedEventHandler handler) {
handler.handleEvent(this);
}
@Override
public Type getAssociatedType() {
return TYPE;
}
public Contact getSelectedContact(){
return contact;
}
}


And that’s all we need to define events and event-handlers for use with each Presenter’s Event Bus. With that in place, we can now go in to our presenters and add code to post events on to the EventBus.



To accomplish this, the Contact Detail Presenter could now look like the code below. Notice it now has its own event bus and an onContactUpdated() event so the AppController can register interest in a ContactUpdatedEvent. And if the Contact is updated, it will post an event to the Event Bus telling it that a Contact was updated and the AppController will be notified and can react accordingly.



public class ContactDetailPresenter {
private ContactDetailView view;
private HandlerManager eventBus = new HandlerManager(null);
public ContactDetailPresenter(ContactDetailView view){
this.view = view;
}

public void setContact(final Contact contact){
final HasChangeHandlers hasChangeHandlers = view.setFirstName(contact.getFirstName());
hasChangeHandlers.addChangeHandler(new ChangeHandler() {

public void onChange(ChangeEvent event) {
eventBus.fireEvent(new ContactUpdatedEvent(contact));
}
});
}

public void onContactUpdated(ContactUpdatedEventHandler updatedEventHandler){
eventBus.addHandler(ContactUpdatedEvent.TYPE, updatedEventHandler);
}
}


With this in place, we can now imagine that the ContactList would take a similar approach to enable an AppController to handle a ContactSelectedEvent. Here’s some code from the App Controller that wires up the events:




public void wireUpEvents(){
contactList.onContactSelected(new ContactSelectedEventHandler() {

public void handleEvent(ContactSelectedEvent contactSelectedEvent) {
contactDetail.setContact(contactSelectedEvent.getSelectedContact());
}
});

contactDetail.onContactUpdated(new ContactUpdatedEventHandler() {

public void handleEvent(ContactUpdatedEvent contactUpdatedEvent) {
contactList.updateContact(contactUpdatedEvent.getUpdatedContact());
}
});
}


Here’s an approximate sequence showing the event-registration process:



image



And here’s an approximate sequence showing the event-handling:



image









Note on Application State: The examples in this post show event handlers being constructed with a Contact object. Depending upon the type of application being built, this may not be a good idea for all these event handlers in different Presenters to have a handle to the exact same object. It’s probably better to have application state managed in a single place and only let presenters have copies of this state. I’ll research application state some more and write a post on that when I have a pattern I’m comfortable with.











Note on changes to this post: This chapter is a version 2. I changed this chapter after reading this article http://code.google.com/webtoolkit/doc/latest/tutorial/mvp-architecture.html. In the first version, I had all UI Components listening on an application-wide event bus and there was no AppController. The ContactDetailPresenter listened for ContactSelectedEvents and would update itself when a ContactSelectedEvent was raised. This meant that the UI component was essentially built for pages that raised ContactSelectedEvents; after reading this article, I realized this coupling is unnecessary and potentially limiting. I like the AppController concept better so that it can be the thing handling events that any particular UI component raises. Now the AppController listens for the ContactSelectedEvent and simply tells the ContactDetailPresenter to present a particular Contact. The ContactDetailPresenter need to know nothing of ContactSelectedEvents. The one thing I did different from the article is that I have an event bus be wholly managed by a presenter; whereas, the article shows an application-wide event bus passed in to the presenters. I personally like the presenter-owned event bus better since the presenter simply support handlers (e.g. ContactListPresenter.onContactSelected()) only for those events it can fire. This, in my opinion, keeps the contract of the presenter more clear. This approach is similar to how Widgets deal with their events and tell its clients how, for example, they can register interest in an OnClick event.


3 comments:

  1. After reading this doc -- http://code.google.com/webtoolkit/doc/latest/tutorial/mvp-architecture.html -- I'm going to refactor this event-handling post so an "AppController" is responsible for coordinating events among UI Components. I like that approach better than having a UI Component listen for events and update itself as I originally approached it.

    ReplyDelete
  2. Nice job on this series of posts. I hope you keep it going.

    Why do you prefer the AppController? It seems to me that the AppController is going to grow out of control as the application grows if it is the only class responsible for coordinating events. Also, once you have a Contacts app built and you decide you want to include it in one pane of an email app, wouldn't you have to migrate the AppController logic up to the email app?

    I'm not saying that there should be no AppController. In fact, changing the window title probably belongs there. Beyond that, I'm wondering if it should just bootstrap the highest level widget in the application and let it go from there.

    ReplyDelete
  3. Hi Brian,

    Good point about the AppController growing out of control.

    On the other hand, I wonder about a UI Component's ability to be reusable if it subscribes and posts to a page-wide event bus.

    Considering a GWT Widget... A Button does not post an OnClick event to a page-wide eventbus. It simply has its own internal eventbus and has a very specific addClickListener() method so that something external to it can register interest in that event and coordinate a response in the application to that event. And then display is changed via specific display-change methods such as setText(). I think this makes for maximum reusability of the Button regardless of what events are flying around a page.

    It seems useful for Presenters to follow this model, too -- have very specific event-registration methods such as onContactUpdated() for the ContactDetailPresenter and then display-change methods such as displayContact() on the ContactDetailPresenter. This way, the state of a ContactDetailPresenter is not based on what events are going on a page-wide event bus. For example, if the ContactDetailPresenter can only display a Contact when/if something on the page posts a ContactSelectedEvent on to a page-wide event bus, then it seems like that could get annoying as you try to use that component in other places. I say this because the other place may not be posting a ContactSelectedEvent from anywhere. So... if the ContactDetailPresenter won't know when to display a contact, what will? I think, for a simple page, this is something like an AppController. For a complex page, this is something like a parent preseneter, some Presenter that can coordinate events across Presenters. Then to your use case of including it in the email app, the parent, coordinating presenter goes along with the ContactList and ContactDetailPresenter?

    As you may tell, I am somewhat on the fence about it all. I'll probably need more experience with a "real", complex app to know for sure which model I like better. Or more likely, it will be one of these things where it just depends on the app. which approach is better.

    - Eric

    ReplyDelete

Followers