About

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

Archive

Subscribe

Atom

Blog Partners