The Model-View-Presenter (MVP) pattern provides an answer to this. Applying the MVP pattern in GWT applications is described in this two-part article: Large scale application development and MVP
In this post, I will demonstrate a modified version of the example code of this article series to show how Errai makes implementing a MVP application in GWT even easier and how Errai helps to further decouple the individual components. The source code of the modified example can be found on GitHub: https://github.com/csadilek/errai-mvp-demo
So, let's take a look at the individual components used in MVP and the modifications and improvements made to it using Errai.
The Model
The business objects used in this example (Contact and ContactDetail) remain more or less unchanged. The @Portable annotation was added to mark them as serializable by Errai. Errai marshalling was designed for maximum flexibility and to impose as few limitations to serializable classes as possible (see our documentation for details). The classes do not need to (but may of course) implement java.io.Serializable.
The View
The views contain only UI layout code. They have no knowledge of the model, other views, transitions and contain no application logic. Errai can improve view implementations by providing the ability to inject UiBinders.
Without Errai:
public class EditContactView extends Composite implements EditContactPresenter.Display {
interface EditContactViewUiBinder extends UiBinder<Panel, EditContactView> {}
private static EditContactViewUiBinder uiBinder =
GWT.create(EditContactViewUiBinder.class);
...
}
With Errai:
public class EditContactView extends Composite implements EditContactPresenter.Display {
@Inject
UiBinder<Panel, EditContactView> uiBinder;
...
}
The Presenter
The presenters contain all the application logic: view transitions, RPCs, etc. In the original example, the RPC proxies, the event bus and the view instances are passed to the presenters as constructor arguments. Using Errai, we can now inject all these components.
@Dependent
public class EditContactPresenter implements Presenter {
public interface Display {
...
}
@Inject
private Caller<ContactsService> contactsService;
@Inject
private HandlerManager eventBus;
@Inject
private Display display;
...
}
This might seem like a minor improvement at first, but once your application grows this will become more than handy. Let's say you come to a point where all your presenters need an additional RPC proxy. Without dependency injection, you would have to change all constructors and all call sites!
The GWT/RPC proxy was replaced with an Errai RPC proxy (the injected Caller). Errai RPC does not require an asynchronous interface nor a servlet based service implementation on the server. It can even be used to invoke methods on a server-side CDI bean (see our documentation for details).
The Application Controller
The application controller handles logic that is not specific to any presenter and also deals with history management and view transitions. It needs access to the event bus (which again we can inject using Errai IOC) to react on application events that trigger view transitions.
@ApplicationScoped
public class AppController implements Presenter, ValueChangeHandler<String> {
@Inject
private HandlerManager eventBus;
@Inject
private IOCBeanManager manager;
...
}
It also needs to be able to create new presenter instances when the browser's history stack changes. To create new managed instances of presenters, we use Errai's client-side bean manager that will make sure all the injection points of presenters are satisfied and the instance is ready to use.
public void onValueChange(ValueChangeEvent<String> event) {
String token = event.getValue();
if (token != null) {
Presenter presenter = null;
if (token.equals("list")) {
IOCBeanDef<ContactsPresenter> bean = manager.lookupBean(ContactsPresenter.class);
if (bean != null) {
presenter = bean.getInstance();
}
} else if (token.equals("add") || token.equals("edit")) {
IOCBeanDef<EditContactPresenter> bean =
manager.lookupBean(EditContactPresenter.class);
if (bean != null) {
presenter = bean.getInstance();
}
}
if (presenter != null) {
presenter.go(container);
}
}
}
The EntryPoint
Since Errai allows us to use dependency injection for all the required components, the main difference here is that we no longer have to pass the RPC proxies and the event bus instance to the app controller which in turn would have to forward it to the presenters and views.
The @Produces method provides the event bus instance to all the event bus injection points in all the presenters. The application controller itself is also just injected and started as soon as the app is finished initializing (@AfterInitialization).
@EntryPoint
public class Contacts {
private HandlerManager eventBus = new HandlerManager(null);
@Inject
private AppController appController;
@AfterInitialization
public void startApp() {
appController.go(RootPanel.get());
}
@Produces
private HandlerManager produceEventBus() {
return eventBus;
}
}
So to sum it up, Errai's IOC container helps implementing the MVP pattern by further decoupling the individual components and by alleviating boilerplate code for UiBinders and RPC proxies.
Future Work
An extension that we are currently working on is the @MockFor annotation that can be used to replace an injected type with a mocked type for unit testing. During tests, a presenter could then get the mocked view injected in place for the actual view for example. The presenters can then be tested using a plain JUnit test instead of an integration test.
@MockFor(EditContactsView.class)
public class EditContactsMockView implements EditContactPresenter.Display {
...
}
As always, feedback is welcome and appreciated!
Thank you for the sample. I plan to use it as a model for the current project I am working on.
ReplyDeleteYou're welcome! Be sure to stop by our project forum or IRC channel in case of questions or suggestions for improvement.
DeleteI am having trouble compiling code derived from the template you describe here. I have:
ReplyDelete@Dependent
public class ViewPresenter implements Presenter {
public interface Display {
Widget asWidget();
}
@Inject
private HandlerManager eventBus;
}
and
@EntryPoint
public class ForumClient {
private HandlerManager eventBus = new HandlerManager(null);
@Inject
private AppController appController;
@AfterInitialization
public void startApp() {
appController.go(RootPanel.get());
}
@Produces
public HandlerManager produceEventBus() {
return eventBus;
}
}
but I am getting the following error:
Rebinding org.jboss.errai.ioc.client.api.Bootstrapper
Invoking generator org.jboss.errai.ioc.rebind.IOCGenerator
Generating Extensions Bootstrapper...
Rebinding org.jboss.errai.ioc.client.api.Bootstrapper
Invoking generator org.jboss.errai.ioc.rebind.IOCGenerator
Generating Extensions Bootstrapper...
[WARN] For the following type(s), generated source was never committed (did you forget to call commit()?)
[WARN] org.jboss.errai.ioc.client.api.BootstrapperImpl
[ERROR] Errors in 'jar:file:/C:/Users/oli/.m2/repository/org/jboss/errai/errai-ioc/2.0.Beta3/errai-ioc-2.0.Beta3.jar!/org/jboss/errai/ioc/client/Container.java'
[ERROR] Line 39: Rebind result 'org.jboss.errai.ioc.client.api.BootstrapperImpl' could not be found
Scanning for additional dependencies: jar:file:/C:/Users/oli/.m2/repository/org/jboss/errai/errai-marshalling/2.0.Beta3/errai-marshalling-2.0.Beta3.jar!/org/jboss/errai/marshalling/client/api/MarshallerFramework.java
Computing all possible rebind results for 'org.jboss.errai.marshalling.client.api.MarshallerFactory'
Rebinding org.jboss.errai.marshalling.client.api.MarshallerFactory
Invoking generator org.jboss.errai.marshalling.rebind.MarshallersGenerator
Generating Marshallers Bootstrapper...
Rebinding org.jboss.errai.marshalling.client.api.MarshallerFactory
Invoking generator org.jboss.errai.marshalling.rebind.MarshallersGenerator
Generating Marshallers Bootstrapper...
Rebinding org.jboss.errai.marshalling.client.api.MarshallerFactory
Invoking generator org.jboss.errai.marshalling.rebind.MarshallersGenerator
Generating Marshallers Bootstrapper...
Rebinding org.jboss.errai.marshalling.client.api.MarshallerFactory
Invoking generator org.jboss.errai.marshalling.rebind.MarshallersGenerator
Generating Marshallers Bootstrapper...
Rebinding org.jboss.errai.marshalling.client.api.MarshallerFactory
Invoking generator org.jboss.errai.marshalling.rebind.MarshallersGenerator
Generating Marshallers Bootstrapper...
Rebinding org.jboss.errai.marshalling.client.api.MarshallerFactory
Invoking generator org.jboss.errai.marshalling.rebind.MarshallersGenerator
Generating Marshallers Bootstrapper...
[ERROR] Cannot proceed due to previous errors
------------------------------------------------------------------------
BUILD FAILURE
------------------------------------------------------------------------
Please use the forum or our IRC channel. We can help with troubleshooting there. Also check out the code for this example at https://github.com/csadilek/errai-mvp-demo . It compiles/runs fine.
DeleteWill the application controller handle code splitting if used this way?
ReplyDeleteWhy you use @Dependent scope for presenters?
ReplyDeleteThis way presenter is created every time History.newItem is invoked. But where is garbage collector? You never remove old preseters. And this is good place for memory leaking.
Very good information, it will help us alot.
ReplyDeleteCustom Web Application Development Company
Web Development Company
Thanks for sharing such great information about application development.
ReplyDeleteApp Development Companies in India
I read that Post and got it fine and informative. Please share more like that. Great Article it its really informative and innovative.
ReplyDeleteBrand Development Company | Travel Technology Software | Software Development Solutions | Website Design Company in India | Mobile App Development Solution
This comment has been removed by the author.
ReplyDeleteI'm very happy being Herpes free now. It was a sad incident that was announced to me after the check up in the hospital and I was diagnosed of HSV 2. I thank God now for using Dr.odey Abang to cure my virus. I'm not ashamed to say this because no virus of such can be detected in me. I'm Charlotte from Columbia. I thought about it many Times when I heard about this Herbal cures for Herpes. I was really happy when I came across blogs of comments of Doctors who run cures sicknesses and was comfortable to try Dr. Abang from patients testimony I came across here on my online page. I knew now they are real Africa herbalists who run cures for Herpes. There's every opportunity to be cure with natural herbs, because all medical prescriptions are derived from herbs and roots. Its really hard time living with Herpes on drugs which can't get you cure. I tried this and I can boost of myself now as a woman. I need to be loved not to lost, get your instant cure to all sicknesses from Dr, Odey Abang.
ReplyDeleteHe cures HSV,HPV,Cancer,low spam count and much more from the evidence I saw 💯 % sure no site effects with active immune booster
Email him for you cure
Odeyabangherbalhome@gmail.com
WhatsApp/calls
+2349015049094