Introduction to the MVP pattern: a GWT example
The MVP ( Model – View – Presenter) pattern can be seen as as an evolution or advanced form of the MVC (Model-View-Controller) pattern. It may not be easy to grasp from a programmatical point view at the begining, but the theory is pretty clear. To provide a better explanation of the MVP pattern, let’s compare it and contrast it with MVC.
MVC allows decoupling the user interface components from the model. It uses the controller to switch from a view to another and to respond to events such as changing context, posting a form,..etc. However, The view can directly invoke functionalities from the model such as checking a value from the database or making a calculation. the view in MVP does not. MVP takes away the intelligence from the View and adds it to the controller which makes a Presenter. The view is completely passive and every interaction of the view with the model is done through the Presenter. Many argue that the main reason for using MVP over MVC is testability. By removing intelligence or business logic from the view, we reduce risk associated with not testing the view. Usually, unit tests are only applied to business logic and the model. So, MVP seems to be convenient for UI oriented applications such as the ones that are based on GWT.
In this tutorial, we will go through an example of basic MVP pattern in a GWT application. Our application has two views: a login page and a main page. Upon successful login, the user is directed to the main page, and on logout the user goes back to the login page.
Requirements: Eclipse, GWT plugin.
The application structure looks something like:
Let’s start by implementing our views.
LoginView.java
public class LoginView implements HasWidgets{
HorizontalPanel container;
Label loginLabel;
Label passwordLabel;
TextBox loginField;
PasswordTextBox passwordField;
Button loginButton;
public LoginView(){
container = new HorizontalPanel();
loginField = new TextBox();
loginButton = new Button("Login");
passwordField = new PasswordTextBox();
loginLabel = new Label("Login");
passwordLabel = new Label("Password");
container.add(loginLabel);
container.add(loginField);
container.add(passwordLabel);
container.add(passwordField);
container.add(loginButton);
}
@Override
public Widget asWidget() {
return container;
}
@Override
public void add(Widget w) {
container.add(w);
}
@Override
public void clear() {
container.clear();
}
@Override
public Iterator<widget> iterator() {
return container.iterator();
}
@Override
public boolean remove(Widget w) {
return container.remove(w);
}
}
MainPageView.java
public class MainPageView implements HasWidgets {
VerticalPanel container;
HorizontalPanel leftPanel;
HorizontalPanel rightPanel;
Button logout;
@Override
public HasClickHandlers getLogoutButton() {
return logout;
}
public MainPageView(){
leftPanel = new HorizontalPanel();
rightPanel = new HorizontalPanel();
container = new VerticalPanel();
logout = new Button("Logout");
container.add(logout);
container.add(leftPanel);
container.add(rightPanel);
}
@Override
public Widget asWidget() {
return container;
}
@Override
public void add(Widget w) {
container.add(w);
}
@Override
public void clear() {
container.clear();
}
@Override
public Iterator<widget> iterator() {
return container.iterator();
}
@Override
public boolean remove(Widget w) {
return container.remove(w);
}
@Override
public Button getButton() {
return logout;
}
}
Notice that the views are only UI components and do no contain any business logic.
Next, we need to build our presenters which will control the behavior and interactions of our views.
LoginPresenter.java
public class LoginPresenter {
public interface Display{
HasClickHandlers getLoginButton();
Widget asWidget();
LoginView getViewInstance();
}
//event bus used to register events
final HandlerManager eventBus;
final Display view;
public LoginPresenter(Display view, HandlerManager eventBus){
this.eventBus = eventBus;
this.view = view;
}
public void bindEvents(){
view.getLoginButton().addClickHandler(new ClickHandler(){
@Override
public void onClick(ClickEvent event) {
// trigger event using eventBus
eventBus.fireEvent(new LoginEvent());
}
});
}
public void go(final HasWidgets container){
bindEvents();
container.clear();
container.add(view.getViewInstance().asWidget());
}
public Display getView(){
return view;
}
}
MainPagePresenter.java
public class MainPagePresenter {
public interface Display{
HasClickHandlers getLogoutButton();
Widget asWidget();
MainPageView getViewInstance();
Button getButton();
}
final Display display;
final HandlerManager eventBus;
public MainPagePresenter(Display display, HandlerManager eventBus){
this.display = display;
this.eventBus = eventBus;
}
public void init(){
display.getLogoutButton().addClickHandler(new ClickHandler(){
@Override
public void onClick(ClickEvent event) {
// use the event bus to trigger the event
eventBus.fireEvent(new LogoutEvent());
}
});
}
public void go(final HasWidgets container){
init();
container.clear();
container.add(display.asWidget());
}
public Display getView(){
return display;
}
}
Notice that both presenters have an interface called Display. This interface needs to be implemented by the view to allow the Presenter to access components of the view. This interface serves as the communication layer between the Presenter and the View. We are going to make each view implements the Display interface of its presenter :
public class LoginView implements HasWidgets, LoginPresenter.Display {
//...other methods
@Override
public HasClickHandlers getLoginButton() {
//return button to implement its events in the Presenter
return loginButton;
}
@Override
public LoginView getViewInstance() {
return this;
}
@Override
public Widget asWidget() {
return container;
}
}
public class MainPageView implements HasWidgets, MainPagePresenter.Display {
//...other methods
@Override
public MainPageView getViewInstance(){
if(instance == null)
return new MainPageView();
else
return instance;
}
@Override
public HasClickHandlers getLogoutButton() {
return logout;
}
}
Finally, we need to implement the AppController, which is the application supervisor. It handles all events, and context changes. The AppController is also used to instantiate the application.
public class AppController {
HandlerManager eventBus;
LoginPresenter loginPage;
HasWidgets container;
public AppController(HandlerManager manager){
this.eventBus = manager;
loginPage = new LoginPresenter(new LoginView(), eventBus);
bindEvents();
}
public void bindEvents(){
eventBus.addHandler(LoginEvent.TYPE, new LoginEventHandler(){
@Override
public void onLogin(LoginEvent event) {
// TODO Auto-generated method stub
//if login successful
MainPagePresenter mainpage = new MainPagePresenter(new MainPageView(), eventBus);
container = mainpage.getView().getViewInstance();
mainpage.go(RootPanel.get());
}
});
eventBus.addHandler(LogoutEvent.TYPE, new LogoutEventHandler(){
@Override
public void onLogout(LogoutEvent event) {
loginPage.go(RootPanel.get());
}
});
}
public void goTo(HasWidgets page){
this.container = page;
loginPage.go(page);
}
}
We can now run our app in the EntryPoint class:
public class MVPexample implements EntryPoint {
public void onModuleLoad() {
HandlerManager eventBus = new HandlerManager(null);
AppController app = new AppController(eventBus);
app.goTo(RootPanel.get());
}
}
Interesting Readings about MVP:
http://www.codeproject.com/Articles/288928/Differences-between-MVC-and-MVP-for-Beginners
http://martinfowler.com/eaaDev/PassiveScreen.html
Full example at: https://github.com/gwidgets/mvpexample.git