Hi,
*WALL OF TEXT BEGINS ;) *
We also encountered this problem in our app.
In our case, our root presenter did not need to hold references to our
child presenter(s). In this case, you only need to solve the problem
you mentioned: "If ContactView has EmailView injected, we get into a
dilemma where we don't have the Presenter instantiated". So I did
something like this:
-------------------------
We are using gwt-presenter. If you extend "WidgetPresenter" (
http://code.google.com/p/gwt-presenter/source/browse/trunk/src/main/java/net/customware/gwt/presenter/client/widget/WidgetPresenter.java
), you can do the following in your view:
public class ContactView extends VerticalPanel implements
ContactPresenter.View {
private final Provider<EmailPresenter> emailPresenterProvider;
@Inject
public ContactView(Provider<EmailPresenter> emailPresenterProvider) {
emailPresenterProvider = emailPresenterProvider;
}
private Widget createEmailView() {
return emailPresenterProvider.get().getDisplay().asWidget();
}
private void buildWidget() {
add(new HtmlPanel("Hi there");
// Create 3 different email views, with 3 different
presenters instantiated
add(createEmailView());
add(createEmailView());
add(createEmailView());
}
}
The problem is that you make your views aware of the presenters. But
since you unit-test the presenters, it won't pose a problem while unit-
testing.
Another solution if you want to abstract the presenter from the view,
would be to use a provider method in your GIN module:
@Provides
EmailWidget createView(EmailPresenter presenter) {
return presenter.getDisplay().asWidget();
}
and then inject a Provider<EmailWidget> in your view.
Each call to emailWidgetProvider.get() will then create a new
presenter (which gets injected with a new view), give it to the
provider method, and return the associated widget to be used by the
view. This solves the problem of "not having the presenter
instantiated".
-------------------------
Now, this "simple case" works because we supposed our ContactPresenter
does not need to access the EmailPresenters. You can avoid these kind
of dependencies by communicating between the presenters through the
eventbus. But you won't always be able to do so, and in the real world
you will often want to be able to call methods on your child
presenters.
I think I would do something like this:
public class ContactPresenter extends
WidgetPresenter<ContactPresenter.View> {
public static interface View extends WidgetDisplay {
void add(WidgetDisplay display);
void remove(WidgetDisplay display);
}
private final Provider<EmailPresenter> emailPresenterProvider;
private Contact contact;
private Map<Email, EmailPresenter> emailMap = new HashMap<Email,
EmailPresenter>();
public ContactPresenter(ContactView display, EventBus eventBus,
Provider<EmailPresenter> emailPresenterProvider,
ContactModel contactModel) {
super(display, eventBus);
this.emailPresenterProvider = emailPresenterProvider;
}
void onBind() {
registerHandler(eventBus.addHandler(EmailAddedEvent.getType(),
new EmailAddedHandler() {
void onEmailAdded(EmailAddedEvent event) {
// should this ContactPresenter handle this email?
if (contact.equals(event.getContact())) {
addEmail(event.getEmail());
}
}
}));
registerHandler(eventBus.addHandler(EmailRemovedEvent.getType(),
new EmailRemovedHandler() {
void onEmailRemoved(EmailRemovedEvent event) {
// should this ContactPresenter handle this email?
if (contact.equals(event.getContact())) {
removeEmail(event.getEmail());
}
}
}));
}
void addEmail(Email email) {
EmailPresenter emailPresenter = emailPresenterProvider.get();
emailPresenter.setEmail(email);
emailMap.put(email, emailPresenter);
display.add(emailPresenter.getDisplay());
}
void removeEmail(Email email) {
EmailPresenter emailPresenter = emailMap.get(email);
display.remove(emailPresenter.getDisplay());
emailPresenter.destroy();
emailMap.remove(email);
}
void onUnbind() {
// handlers are de-registered by BasicPresenter superclass
// since we called registerHandler() in the onBind() method
for (Email email : emailMap.keySet()) {
removeEmail(email);
}
}
// Should be injected in the constructor with assisted inject or with
a
// factory?
void setContact(Contact contact) {
this.contact = contact;
for (Email email : contact.getEmails()) {
addEmail(email);
}
}
}
class ContactView extends VerticalPanel implements
ContactPresenter.View {
// Create the view and so on...
void add(WidgetDisplay display) {
add(display.asWidget());
}
void remove(WidgetDisplay display) {
remove(display.asWidget());
}
}
(some things might be missing, since I wrote that without any dev
environment... but the main ideas are there)
-------------------------
All of this works because the Presenter has a getDisplay() method, and
the WidgetDisplay has a asWidget() method returning the underlying
widget. One would think that the asWidget() method is bad when unit-
testing presenters, but you can simply return null in your
WidgetDisplay mock.
Actually, in our application, since we use SmartGwt, where the high-
level component is a "Layout" instead of a "Widget" we created a
LayoutPresenter and a LayoutDisplay (with a
com.smartgwt.client.widgets.layout.Layout asLayout() method). We also
have a WindowPresenter / WindowDisplay, where the WindowPresenter
superclass handles some of the window logic (with a openWindow()
method that calls bind() and then shows the window... and a closeWindow
() that calls unbind() and then hides the window...).
What do you think of this kind of architecture? I'm interested in
other ideas and best practices as well.
Regards,
-Etienne