Developing a GWT application using MVP

We'll develop an application in Eclipse. Visit the following URL to download the Google Plugin for Eclipse:

https://developers.google.com/eclipse/docs/download

Install the plugin and create a new web application. The following screenshot shows this:

Developing a GWT application using MVP

A new application wizard will appear on the screen. To create a new web application, perform the following steps:

  1. Enter the project name as OutstandingBills and the package name as com.packt.billing.
  2. Check the Google SDKs checkbox and select the default SDK radio button.
  3. If you have downloaded a separate GWT binary, provide the path and configure the SDK. Also, check the Generate project sample code checkbox; it will create the necessary files we need to develop a GWT application. We'll change the filenames as required.

    The following screenshot displays the settings:

    Developing a GWT application using MVP

    The preceding steps will generate the following project structure:

    Developing a GWT application using MVP
  4. Open the OutstandingBill.gwt.xml file; this file contains the project metadata, for example, the <entry-point> classname. The EntryPoint is the starting point in GWT applications.
  5. Open the com.packt.billing.client.OutstandingBills entry-point class. It implements the EntryPoint interface and overrides the onModuleLoad() method. This method is invoked during a GWT application loading.

    GWT applications make asynchronous calls to the server and process responses with service callbacks. When the server response comes back, a callback processes the response. All callbacks implement the AsyncCallback interface and the onSuccess() and onFailure() methods. The onFailure method is called when the server encounters any error and throws an exception or error. The onFailure method can take care of the server failure, for example, it can show a proper error message to the user. The onSuccess method is called when the server returns a response and no error occurs on the server side.

  6. Check that Eclipse has generated two interfaces, GreetingService and GreetingServiceAsync, for service invocation. Conventionally, the <ServiceName>Service interface defines the service methods and extends the RemoteService interface as follows:
    @RemoteServiceRelativePath("greet")
    public interface GreetingService extends RemoteService {
      String greetServer(String name) throws IllegalArgumentException;
    }

    A server-side class implements the interface.

    The other <ServiceName>ServiceAsync interface redefines the method, but all methods become void; yet they all take an additional parameter called AsyncCallback:

    public interface GreetingServiceAsync {
      void greetServer(String input, AsyncCallback<String> callback)
      throws IllegalArgumentException;
    }

    Note that greetServer() is a void method and it takes an additional parameter's AsyncCallback<String> callback. If a service method returns ArrayList<Integer>, the callback will look like AsyncCallback<ArrayList<Integer>>. We would rather define the service interface and the async interface relation as follows:

    @RemoteServiceRelativePath("name")
    public interface SomeService extends RemoteService {
      T someMethod(String name) throws E;
    }

    In the preceding code, T is any Java type, such as object, integer, or string, and E is any exception, such as IllegalStateException.

    The async interface will look like the following code snippet:

    public interface SomeServiceAsync {
      void someMethod (String input, AsyncCallback<T> callback) throws E;
    }
  7. The GWT compiler translates the Java code to JavaScript. Select the project and click on the red-colored GWT Compile icon from the toolbar, or you can right-click on the project and then select Google from the pop-up menu and click on the GWT Compile menu item, as shown in the following screenshot:
    Developing a GWT application using MVP

    The preceding step will compile the code and generate JavaScript under the war folder; the following screenshot displays the location:

    Developing a GWT application using MVP

    Note that the compilation has generated a JavaScript file called outstandingbills.nocache.js. This JavaScript file is responsible for rendering the application. From the HTML or JSP file, you need to provide the path of the script file, as shown in the following line of code:

    <script type="text/javascript" language="javascript" src="outstandingbills/outstandingbills.nocache.js"></script>
    

We'll now build an application to handle outstanding hotel bills. The user interface will display a textbox and a Query Bill button. The user will enter a room number and then hit the button to query the current outstanding bill. A pop up will be displayed with the bill details and payment options. The following are the steps to build the application:

  1. Create a serializable class Bill in the com.packt.billing.client package, and add the following members and getters/setters:
    private String details;
    private BigDecimal payable;
  2. Create a service interface to retrieve the outstanding bills and make the payment. Create a BillingService interface in the com.packt.billing.client package with the following details:
    @RemoteServiceRelativePath("bill")
    public interface BillingService extends RemoteService {
      public Bill retrieve(String roomNumber);
      public boolean pay(String roomNumber, BigDecimal amount);
    }
  3. Create an async interface with the following information:
    public interface BillingServiceAsync {
      public void retrieve(String roomNumber, AsyncCallback<Bill> callback);
      public void pay(String roomNumber, BigDecimal amount, AsyncCallback<Boolean> callback);
    }
  4. Create a service implementation class com.packt.billing.server.BillingServiceImpl, implementing BillingService:
    @SuppressWarnings("serial")
    public class BillingServiceImpl extends RemoteServiceServlet    implements BillingService {
      @Override
      public Bill retrieve(String rommNumber) {
        // TODO Auto-generated method stub
        return null;
      }
      @Override
      public boolean pay(String roomNumber, BigDecimal amount) {
        // TODO Auto-generated method stub
        return false;
      }
    }

    Usually, two separate projects should be created for the GWT service and service implementation, namely, a contract project with service and async interfaces, and an implementation project that implements the service interface. Both the projects should be deployed in the web/application server.

  5. Modify the web.xml file under the war folder and add the following entries to define the BillingServiceImpl servlet and to map the URL to BillingServiceImpl. All HTTP requests with the /outstandingbills/bill URL pattern will be mapped to the BillingServiceImpl servlet:
      <servlet>
        <servlet-name>billingServlet</servlet-name>
        <servlet-class>
          com.packt.billing.server.BillingServiceImpl
        </servlet-class>
      </servlet>
      
      <servlet-mapping>
        <servlet-name>billingServlet</servlet-name>
        <url-pattern>/outstandingbills/bill</url-pattern>
      </servlet-mapping>
    
  6. Open the OutstandingBills class and replace all GreetingService references with BillingService; remove everything from the onModuleLoad() method. The following is the modified class:
    public class OutstandingBills implements EntryPoint {
    
      private final BillingServiceAsync service = GWT    .create(BillingService.class);
    
      /*** This is the entry point method. ***/
      @Override
      public void onModuleLoad() {
      }
    }

    Did you notice the service definition? The BillingServiceAsync interface is created with GWT.create(BillingService.class). Basically, a remote service proxy is created to talk to the server-side BillingServiceImpl class. The BillingService interface is annotated with @RemoteServiceRelativePath("bill"). Any service call will have the /bill token in the URL. We will set up web.xml to map /bill to the BillingServiceImpl servlet.

  7. Now modify the BillingServiceImpl class to have some hardcoded room numbers and bills. In constructor, populate a HashMap with room numbers and payable bill amounts. We'll use room numbers from 1 to 5000 and generate random payable amounts. The retrieve method will look up the HashMap for the outstanding payable and the pay method will deduct the amount from the HashMap. The following is the modified class:
      public class BillingServiceImpl extends RemoteServiceServlet    implements  BillingService {
        private Map<String, BigDecimal> billMap = new HashMap<String, BigDecimal>();
    
        public BillingServiceImpl() {
          Random random = new Random();
          for (int i = 1; i < 5000; i++) {
            billMap.put(String.valueOf(i),new BigDecimal(random.nextInt(1000000)));
          }
       }
        @Override
        public Bill retrieve(String roomNumber) {
          BigDecimal payable = billMap.get(roomNumber);
          Bill bill = new Bill();
          if (payable != null) {
            bill.setDetails("Accomodation charge for room#" + roomNumber + " and payable amount="+ payable.doubleValue());
          }
          bill.setPayable(payable);
          return bill;
        }
        @Override
        public boolean pay(String roomNumber, BigDecimal amount) {
          BigDecimal payable = billMap.get(roomNumber);
          if(payable != null){
            payable = payable.subtract(amount);
            billMap.put(roomNumber, payable);
            return true;
          }
          return false;
        }
      }

    We will have two views—the initial query view with the room number textbox and query button, and the bill details view with bill details, payment textbox, and the make payment button; we'll call them QueryView and DetailsView, respectively.

    We've already talked about the view interfaces in the MVP section. We'll create two view interfaces with abstract DOM elements, such as HasClickHandlers or HasValue, to represent QueryView and DetailsView in the com.packt.billing.client.view package.

  8. We need the room number when the Query button is pressed. So we'll add a method to the view to get the room number value. We need to intercept the button click, so we'll add a method to the view to get a clickable object, so that we can add a handler to the object to intercept the button click. The view should not add any handlers to any DOM object. Rather, it should provide the handle to the presenter to handle the logic. The following is the QueryView body:
    public interface QueryView {
      Widget asWidget();
      HasClickHandlers getQueryButton();
      HasValue<String> getRoomNumber();
    }

    The asWidget() method returns a widget. We'll add the widget to the container. The getQueryButton() method represents the Query button and the getRoomNumber() represents the value entered in the room number textbox. Now we need the actual view implementation.

  9. Create a QueryViewImpl class in the com.packt.billing.client.view package, implementing the QueryView interface and extending the Composite class:

    A Composite class is a type of a widget that can wrap another widget, hiding the wrapped widget's methods. When added to a panel, a Composite class behaves exactly as it should if the widget it wraps had been added. The Composite class is useful for creating a single widget out of an aggregate of multiple other widgets contained in a single panel.

    We'll use the Label, TextBox, Button, FlexTable, and HorizontalPanel GWT widgets to represent the view. The Label, TextBox, and Button GWT widgets will be added to FlexTable. Flexible table creates cells on demand. It can be jagged (that is, each row can contain a different number of cells), and individual cells can be set to span multiple rows or columns. The FlexTable widget will be added to the HorizontalPanel widget. The following is the implementation. The beauty of having a view interface is that you can change the view implementation without altering the code in the presenter. You can use a Textbox or PasswordTextBox widget to represent HasValue; the presenter won't know about the actual implementation. This is how the view is abstracted from the presenter or rather, the view is loosely coupled from the business logic/presenter:

    public class QueryViewImpl extends Composite implements QueryView {
      private HorizontalPanel mainPanel;
      private TextBox roomNumber= new TextBox();
      private Button query = new Button("Query");
    
      public QueryViewImpl(){
        mainPanel = new HorizontalPanel();
        mainPanel.setWidth("100%");
        mainPanel.setHorizontalAlignment(HasHorizontalAlignment      .ALIGN_LEFT);
        FlexTable mainTable = new FlexTable();
        mainTable.setWidth("100%");
        mainTable.setWidget(0, 0, new Label("Room#"));
        mainTable.setWidget(0, 1, roomNumber );
        mainTable.setWidget(0, 2, query );
        mainTable.getCellFormatter().setWidth(0, 0, "5%");
        mainTable.getCellFormatter().setWidth(0, 1, "10%");
        mainPanel.add(mainTable);
        initWidget(mainPanel);
      }
    
      @Override public Widget asWidget() {
        return this;
      }
    
      @Override public HasClickHandlers getQueryButton() {
        return query;
      }
    
      @Override public HasValue<String> getRoomNumber() {
        return roomNumber;
      }
    }

    We'll define the view interface and implementation of DetailsView in the next section when the application is up and running with QueryView.

  10. Similarly, we need two presenters to present the views. We'll define a Presenter interface in the presenter package with following details:
      public interface Presenter {
        void render(final HasWidgets container);
      }

    HasWidgets represents a DOM element on the HTML page, such as a <div> element. GWT renders UI components in that DOM container; we'll refer to it as container. The QueryPresenter will present the initial view and implement the Presenter interface. We need to pass a view interface to the presenter, so we'll pass a QueryView instance to the QueryPresenter. The following is the presenter:

    public class QueryPresenter implements Presenter {
      private final QueryView queryView;
    
      bv public QueryPresenter(QueryView queryView) {
        this.queryView = queryView;
      }
      @Override public void render(HasWidgets container) {
        container.clear();
        container.add(queryView.asWidget());
      }
    }

    In the next section, we'll create the DetailsPresenter to represent the DetailsView.

  11. In a browser, we hit the back button to go back to the previous page. In GWT, there is no previous page, as a single HTML/JSP page displays many views. So to go back or forward to the previous or next view so that we can browse the history. The com.google.gwt.user.client.History class represents the browser history. This class allows you to interact with the browser's history stack. Each item on the stack is represented by a single string referred to as a token. You can create new history items (which have a token associated with them when they are created), and you can programmatically force the current history to move backward or forward.

    History token change is handled by implementing the ValueChangeHandler<T> interface:

      public interface ValueChangeHandler<T> extends EventHandler {
        void onValueChange(ValueChangeEvent<T> event);
      }

    When a history token is changed, the ValueChangeHandler interface is notified. However, before that, the handler needs to be registered to History using the following syntax:

    History.addValueChangeHandler(this);
  12. We'll create an ApplicationController class. This class will implement the Presenter interface and provide the concrete implementation of the render() method. Also, the class will implement the ValueChangeHandler interface and register itself to History to interact with the history. The render method will put a new token START to the History stack to start the view transition. The onValueChange(ValueChangeEvent event) method will be invoked on History value change; this method will check the token value START and create a new Presenter interface to display the initial view. The following is the ApplicationController class:
    public class ApplicationController implements Presenter, ValueChangeHandler<String>{
    
      private static final String BLANK = "";
      private static final String START = "START";
      private HasWidgets container;
    
      public ApplicationController(){
        History.addValueChangeHandler(this);
      }
    }

    The render() method stores the container and checks the History token. If the application is invoked the first time, the History stack will contain a blank string, and then the render() method will add a new token item "START" to the History stack. Otherwise, when you hit refresh, the current state of the History stack is fired so that the same view is rerendered. That way, a user doesn't lose any data. When a new item is pushed or a current history item is fired, the history value changes and then the onValueChange method is invoked:

      @Override
      public void render(HasWidgets container) {
        this.container = container;
    
        if (BLANK.equals(History.getToken())) {
          History.newItem(START);
        } else {
          History.fireCurrentHistoryState();
        }
    
      }

    The onValueChange method checks the history token. If the token is "START", it creates the QueryViewImpl view, instantiates the QueryPresenter, and finally, calls the presenter.render() method to display the view. For multiple views, the history token value will be changed, and depending upon the token value, the appropriate presenter will be instantiated and finally, the render() method will be invoked on the presenter:

    @Override public void onValueChange(ValueChangeEvent<String>   event) {
      String token = event.getValue();
      container.clear();
      Presenter presenter = null;
      if (START.equals(token)) {
        presenter = new QueryPresenter(new QueryViewImpl());
      }
      if (presenter != null) {
        presenter.render(container);
      }
    }
  13. Modify the onModuleLoad() method of the OutstandingBills EntryPoint class to create an instance of ApplicationController and invoke the render method with a DOM ID. The following is the modified method:
      @Override
      public void onModuleLoad() {
        Presenter presenter = new ApplicationController();
        presenter.render(RootPanel.get("dom"));
      }
  14. Modify the OutstandingBills.html file to add a div with id="dom":
    <html>
      <head>
        <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    
        <link type="text/css" rel="stylesheet" href="OutstandingBills.css">
    
    
        <script type="text/javascript" language="javascript" src="outstandingbills/outstandingbills.nocache.js"/>
    
      </head>
    
      <body>
    
        <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1'style="position:absolute;width:0;height:0;border:0">
        </iframe>
    
        <h1>Web Application Starter Project</h1>
    
        <div id="dom"></div>
      </body>
    </html>

    Note that the <script> tag loads the GWT script and the <iframe> tag enables the history mechanism. If this entry is missing, the history token management will not work and the MVP pattern's purpose will be violated.

  15. Right-click on the project and run it as GWT web application, then copy the URL and paste it to a web browser. The following output will be displayed:
    Developing a GWT application using MVP
  16. Now we have the application configured. The next step is to build the DetailsView interface and enable the view transition. We'll start with the new view. Create an interface DetailsView and add the following lines:
    public interface DetailsView {
      Widget asWidget();
      HasClickHandlers getPaymentButton();
      HasClickHandlers getCloseButton();
      HasValue<String> getPaymentAmount();
      void populate(Bill bill);
    }

    The populate() method is used to populate the bill details to the UI, the getCloseButton() method is used to get hold of the close button, the getPaymentButton() method is for the payment button, the getPaymentAmount() method is for the payment textbox, and the asWidget method is used to return the composite.

  17. Create the DetailsViewImpl class for displaying the details view. The following is the code:
    public class DetailsViewImpl extends Composite implements DetailsView {
      private VerticalPanel mainPanel;
      private TextBox amount = new TextBox();
      private Button payment = new Button("Pay");
      private Button close = new Button("Close");
      private Label desc = new Label();
      private Label dueAmt = new Label();
    
      public DetailsViewImpl() {
        mainPanel = new VerticalPanel();
        mainPanel.setWidth("100%");
    
        FlexTable mainTable = new FlexTable();
        mainTable.setWidth("100%");
    
        mainTable.setWidget(0,0, new Label("Desc#"));
        mainTable.setWidget(0,1, desc);
    
        mainTable.setWidget(1,0, new Label("Due#"));
        mainTable.setWidget(1,1, dueAmt);
    
        mainTable.setWidget(2,0,new Label("Pay amount#"));
         mainTable.setWidget(2,1, amount);
    
        mainTable.setWidget(3,0, payment);
        mainTable.setWidget(3,1, close);
    
        mainTable.getCellFormatter().setWidth(0,0, "5%");
        mainTable.getCellFormatter().setWidth(0,1, "60%");
    
        mainTable.getCellFormatter().setWidth(1,0, "5%");
        mainTable.getCellFormatter().setWidth(1,1, "60%");
    
        mainTable.getCellFormatter().setWidth(2,0, "5%");
        mainTable.getCellFormatter().setWidth(2,1, "60%");
    
        mainTable.getCellFormatter().setWidth(3,0, "25%");
        mainTable.getCellFormatter().setWidth(3,1, "60%");
    
        mainTable.getCellFormatter().setAlignment(3, 0, HasHorizontalAlignment.ALIGN_RIGHT, HasVerticalAlignment.ALIGN_MIDDLE);
    
        mainTable.getCellFormatter().setAlignment(3, 1, HasHorizontalAlignment.ALIGN_LEFT, HasVerticalAlignment.ALIGN_MIDDLE);
    
        mainPanel.add(mainTable);
        initWidget(mainPanel);
      }
    
      @Override public Widget asWidget() {
        return this;
      }
    
      @Override public HasClickHandlers getPaymentButton() {
        return payment;
      }
    
      @Override public HasValue<String> getPaymentAmount() {
        return amount;
      }
    
      @Override public void populate(Bill bill) {
        desc.setText(bill.getDetails());
        dueAmt.setText(""+bill.getPayable().doubleValue());
      }
    
      @Override public HasClickHandlers getCloseButton() {
        return close;
      }
    }

    GWT fires GwtEvents to indicate the completion of the task. The class, com.google.gwt.event.shared.GwtEvent, represents the event. This is the root of all GWT events. The user can create custom events to notify if a view change is required (to interact with other views).

    An event is always defined with an event handler such as the following:

    public class SearchEvent extends GwtEvent<SearchEventHandler> {
    }

    Every event comes up with an event handling contract. This contract is known as an event handler.

    Event handlers extend a marker interface com.google.gwt.event.shared.EventHandler.

    The following is an example of an event handler contract:

    public interface SearchEventHandler extends EventHandler {
      void onSearch(SearchEvent event);
    }

    Events are fired to an event bus class: com.google.gwt.event.shared.HandlerManager.

    HandlerManager (also known as event bus) is responsible for adding handlers to event sources and associating those handlers to pass in events.

    The following code snippet is an example of firing events:

    eventBus.fireEvent(new SearchEvent(getSearchText()));

    The following is an example of event handling:

    eventBus.addHandler(SearchEvent.TYPE, new SearchEventHandler() {
      public void onSearch(SearchEvent event) {
        doSearch(event.getRoomNumber());
      }
    });

    Tip

    In an MVP context, when a presenter needs to notify a view change to the system, it fires a GWT event.

    The event handler associated with this event intercepts the event and hands over the control to another presenter. This new presenter renders a new view.

    Our QueryView needs to notify the view change when the user hits the Query button. We'll create a GWT event called SearchEvent and a SearchEventHandler:

    public class SearchEvent extends GwtEvent<SearchEventHandler> {
      private String roomNumber;
    
      public static Type<SearchEventHandler> TYPE = new Type<SearchEventHandler>();
    
      @Override
      public com.google.gwt.event.shared.GwtEvent.Type<SearchEventHandler> getAssociatedType() {
        return TYPE;
      }
    
      @Override
      protected void dispatch(SearchEventHandler handler) {
        handler.onSearch(this);
      }
    
      public String getRoomNumber() {
        return roomNumber;
      }
    
      public void setRoomNumber(String roomNumber) {
        this.roomNumber = roomNumber;
      }
    
    }

    The event handler will look like this:

    public interface SearchEventHandler extends EventHandler {
      void onSearch(SearchEvent event);
    }
  18. We'll modify the QueryPresenter to fire the SearchEvent with the roomNumber when the user hits the Query button. To fire the event, the presenter needs an event bus. Modify the constructor to pass a HandlerManager instance. The following is the modified constructor:
    public QueryPresenter(QueryView view,HandlerManager bus) {
      this.queryView = view;
      this.eventBus = bus;
      queryView.getQueryButton().addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
          SearchEvent searchEvent = new SearchEvent();
          searchEvent.setRoomNumber(queryView.getRoomNumber().getValue());
          eventBus.fireEvent(searchEvent);
        }
      });
    }
  19. Modify the ApplicationController to create a HandlerManager instance and pass it to the modified presenter. The following are the modified constructor and class level members for roomNumber and HandlerManager:
      private HandlerManager eventBus;
      private String roomNumber;
      public ApplicationController(){
        History.addValueChangeHandler(this);
        this.eventBus = new HandlerManager(this);
    
        eventBus.addHandler(SearchEvent.TYPE, new SearchEventHandler() {
          @Override
          public void onSearch(SearchEvent event) {
            roomNumber = event.getRoomNumber();
            History.newItem(SEARCH);
          }
        });
      }
  20. Create a DetailsPresenter to handle the view. The presenter needs to make a service call to check the bill, so we need to pass the BillingServiceAsync instance to the presenter, and we also need to pass the roomNumber string for which the view will be rendered. The following is the presenter:
    public class DetailsPresenter implements Presenter {
      private final DetailsView detailsView;
      private final BillingServiceAsync billingService;
      private final String roomNumber;
    
      public DetailsPresenter(BillingServiceAsync billingService, DetailsView detailsView, String roomNumber) {
        this.detailsView = detailsView;
        this.billingService = billingService;
        this.roomNumber = roomNumber;
      }
    
      @Override
      public void render(final HasWidgets container) {
        container.clear();
        container.add(detailsView.asWidget());
    
    
        billingService.retrieve(roomNumber, new AsyncCallback<Bill>(){
    
          @Override
          public void onSuccess(Bill bill) {
            detailsView.populate(bill);
          }
    
          @Override
          public void onFailure(Throwable caught) {
            Window.alert("Error occured "+caught);
          }
        });
      }
    }

    The render() method makes a service call to get the bill information and then passes that information to the view by making a call to the populate() method.

  21. Modify the ApplicationController to intercept the history value change for the Search token. Modify the constructor to inject the BillingService interface:
    public ApplicationController(BillingServiceAsync billingService){
      this.billingServiceAsync = billingService;
    }

    Modify the onValueChange() method to handle the Search history token class.

    @Override
    public void onValueChange(ValueChangeEvent<String> event) {
      String token = event.getValue();
      container.clear();
      Presenter presenter = null;
      if (START.equals(token)) {
        presenter = new QueryPresenter(new QueryViewImpl(), eventBus);
      }
    
      if(SEARCH.equals(token)){
        presenter = new DetailsPresenter(billingServiceAsync, new DetailsViewImpl(), roomNumber);
      }
      if (presenter != null) {
        presenter.render(container);
      }
    }

    The following will be the output of the new changes:

    Developing a GWT application using MVP
  22. Modify the DetailsPresenter interface to handle the Pay and Close button click. On the Pay button click, we'll make a service call, display the message, and close the view. On the Close button click, we'll put a START item to the history to go back to the initial state. The following is the modified constructor:
    public DetailsPresenter(BillingServiceAsync service, DetailsView view, String rn) {
      this.detailsView = view;
      this.billingService = service;
      this.roomNumber = rn;
    
      detailsView.getCloseButton().addClickHandler(new ClickHandler() {
    
        @Override public void onClick(ClickEvent event) {
          History.newItem("START");
        }
      });
    
      detailsView.getPaymentButton().addClickHandler(new ClickHandler() {
    
        @Override
        public void onClick(ClickEvent event) {
          String amount = detailsView.getPaymentAmount().getValue();
          billingService.pay(roomNumber, new BigDecimal(amount), new AsyncCallback<Boolean>() {
    
            @Override
            public void onFailure(Throwable caught) {
              Window.alert("Error "+caught);
            }
    
            @Override
            public void onSuccess(Boolean result) {
              if(result){
                Window.alert("Posted payment");
                History.newItem("START");
              }else{
                Window.alert("Could not post payment");
              }
            }
          });
        }
      });
    }

We are done with MVP. We should add the validation logic for user entry fields for blank or invalid input. For instance, an error message should be displayed when the room number textbox is blank but a user hits the Query button, or when the payment amount textbox is blank and a user hits the Pay button.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.2.157