пятница, 26 августа 2011 г.

Разработка GWT приложения с использованием MVP и Mvp4G

1) MVC vs MVP 
     MVC (Model-View-Controller) - шаблон проектирования, в котором приложение разделено на три отдельные компоненты: модель данных, пользовательский интерфейс и логика.

  • Модель предоставляет данные и реагирует на запросы от контроллера, изменяя свое состояние 
  • Представление (View) отвечает за отображение информации 
  • Контроллер (Controller) - обрабатывает различные данные, и говорит модели и представлению о необходимости взаимодействия.
 
     В шаблоне проектирования MVP (Model-View-Presenter) логика отделяется от отображения. Presenter для получения и установки данных модели использует View, который определяется как интерфейс. Реализация View содержит ссылку на класс Presenter’а и предоставляет ссылку на себя. Когда происходит событие View - вызывается определенный метод Presentera’а. Далее Presenter получает данные из View, через интерфейс. Затем Presenter вызывает методы модели, и устанавливает данные из модели во View через интерфейс.


2) Mvp4g - превосходная библиотека для управления структуры GWT приложения. Особенно когда используется MVP подход, где Presenter’ы общаются между собой через события (events). Mvp4g позволяет эффективно строить приложение, особенно когда применяются след. приемы построения :
  • Event Bus 
  • Dependency Injection (Внедрение зависимости) 
  • MVP 
  • History Management/Place Service (Управление историей) 
3) Guice - dependency injection фреймворк от Google, который позволяет улучшить тестируемость и модульность кода, избавляет от необходимости написания собственных фабрик.
4) Gin - позволяет автоматически внедрять зависимость в клиентскую часть GWT. 
     Рассмотрим разработку приложения с подходом MVP для построения архитектуры GWT-проекта с использованием Mvp4g. Приложение будет разделено на отправителей(Senders), которые будут отсылать данные и получателей(Receivers), которые будут реагировать на изменения.
Для интеграции Mvp4g в проект нужно добавить след. строку в GWT конфигурационный файл:
<inherits name="com.mvp4g.Mvp4gModule" />
     В classpath проекта должны лежать след. библиотеки:

     Создадим стартовую точку GWT приложения и пропишем ее в конфигурационном файле GWT:
package com.dmitrynikol.gwt.mvp.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.mvp4g.client.Mvp4gModule;

public class Mvp4gApp implements EntryPoint {

    public void onModuleLoad() {
        Mvp4gModule module = (Mvp4gModule) GWT.create(Mvp4gModule.class);
        module.createAndStartModule();
        RootPanel.get().add((Widget) module.getStartView());
    }
}
<entry-point class='com.dmitrynikol.gwt.mvp.client.Mvp4gApp'/>
     Теперь когда проект настроен для использования с Mvp4g, перейдем к созданию других необходимых частей приложения (eventBus, handlers, presenters, view).
     Mvp4g архитектура построена вокруг шины событий (EventBus). EventBus похож на командный центр, который связывает вместе все остальные части. Основная его идея в том, что другие части не знают о друг друге и могут работать независимо в свободно связанной форме. Что в свою очередь означает, что мы получаем защищенное, поддерживаемое и масштабируемое решение. EventBus является очень важной частью, так как в приложении выступает в качестве точки входа. 
     Создадим шину событий:
@Events(startView = MainWidget.class)
public interface AppEventBus extends EventBus {

    @Start
    @Event(handlers = { PresenterHandler.class })
    public void init();

    @Event(handlers = { MainPresenter.class })
    public void showSenderWidget(IsWidget widget);

    @Event(handlers = { MainPresenter.class })
    public void showReceiverWidget(IsWidget widget);

    @Event(handlers = {ReceiverPresenter.class})
    public void setSelectedItem(String genre, String value);
}
  • @Events(startView = MainWidget.class) - говорим, что класс MainWidget будет использоваться в качестве основного view. 
      Часть приложения, которая отвечает за отображения информации(View) состоит из главного view - MainWidget и виджетов для отправки и приема данных(SenderWidget, ReceiverWidget).
public class MainWidget extends Composite implements IMainWidget {
    private static MainWidgetUiBinder uiBinder = GWT.create(MainWidgetUiBinder.class);

    @UiTemplate("MainWidget.ui.xml")
    interface MainWidgetUiBinder extends UiBinder<Widget, MainWidget> {
    }

    @UiField SplitLayoutPanel mainPanel;
    @UiField FlowPanel senderPanel;
    @UiField FlowPanel receiverPanel;

    private List<IsWidget> receiverList = new ArrayList<IsWidget>();

    public MainWidget() {
        initWidget(uiBinder.createAndBindUi(this));
    }

    @Override
    public void addSenderWidget(IsWidget widget) {
        senderPanel.add(widget);
    }

    @Override
    public void addReceiverWidget(IsWidget widget) {
        addInnerReceiverWidget(widget);
        if (!receiverList.isEmpty() && receiverList.size() == 3) {
            HorizontalPanel receiverGameCardPanel = new HorizontalPanel();
            for (IsWidget gameCard : receiverList)
                receiverGameCardPanel.add(gameCard);
            receiverPanel.add(receiverGameCardPanel);
            receiverList.clear();
        }
    }

    private void addInnerReceiverWidget(IsWidget widget) {
        receiverList.add(widget);
    }
}


* This source code was highlighted with Source Code Highlighter.
public class SenderWidget extends Composite implements ISenderWidget {
    private static SenderWidgetUiBinder uiBinder = GWT.create(SenderWidgetUiBinder.class);

    @UiTemplate("SenderWidget.ui.xml")
    interface SenderWidgetUiBinder extends UiBinder<Widget, SenderWidget> {
    }

    @UiField InlineLabel type;
    @UiField ListBox genreList;

    public SenderWidget() {
        initWidget(uiBinder.createAndBindUi(this));
    }

    @Override
    public void showGroup(String group) {
        type.setText(group);
        addStyleName(group);
    }

    @Override
    public HasChangeHandlers getSelectableComponent() {
        return genreList;
    }

    @Override
    public String getSelectedValue() {
        return genreList.getItemText(genreList.getSelectedIndex());
    }

    @Override
    public void displayValues(List<Genre> values) {
        genreList.addItem("");
        for (Genre item : values) {
            genreList.addItem(item.toString());
        }
    }
}


* This source code was highlighted with Source Code Highlighter.
public class ReceiverWidget extends Composite implements IReceiverWidget {
    private static ReceiverWidgetUiBinder uiBinder = GWT.create(ReceiverWidgetUiBinder.class);

    @UiTemplate("ReceiverWidget.ui.xml")
    interface ReceiverWidgetUiBinder extends UiBinder<Widget, ReceiverWidget> {
    }

    @UiField InlineLabel gameType;
    @UiField InlineLabel gameName;

    public ReceiverWidget() {
        initWidget(uiBinder.createAndBindUi(this));
    }

    @Override
    public void showGroup(String group) {
        gameType.setText(group);
        addStyleName(group);
    }

    @Override
    public void setReceivedValue(String value) {
        gameName.setText(value);
    }
}


* This source code was highlighted with Source Code Highlighter.
     К этим виджетам также идут шаблоны(*.ui.xml) из которых генерируется UiBinder.
     Презентерам нужны два аргумента - представление и шина событий. 
  • @Presenter(view = MainWidget.class) - говорим mvp4g системе, какой view этот класс будет обслуживать 
     Обслуживанием view занимаются презентеры. ReceiverPresenter и SenderPresenter наследуют класс AbstractGroupPresenter, где будет хранится тип игры. 
   Не так давно в Mvp4g появилась возможность множественного создания экземпляров презентеров. Презентер должен быть обьявлен так: 
  • @Presenter(view = SenderWidget.class, multiple = true) 
  Использование multiple = true, говорим о том, что мы должны управлять созданием экземпляров презентеров, примерно так: 
  • ReceiverPresenter presenter = (ReceiverPresenter) eventBus.addHandler(ReceiverPresenter.class, false)
    Так мы создаем экземпляр презентера и прикрепляем его к шине событий. После этого они будут готовы слушать события.
     Когда у нас будет множество экземпляров одного и того же презентера, как же заставить их взаимодействовать друг с другом? Нужно их сгруппировать! В примере используется след. событие для связи с выбранным значением(параметр genre определяет группу презентеров):
@Event(handlers = {ReceiverPresenter.class})
public void setSelectedItem(String genre, String value);

     Когда в приложении будет несколько экземпляров ReceiverPresenter и сработает событие, то все подключенные презентеры среагируют на него.
eventBus.setSelectedItem(Game.Action, “ThirdPersonShooter”);
     Презентеры слушают только те события к которым они подписаны, а это зависит от того к какой группе они принадлежат:
ReceiverPresenter presenter = (ReceiverPresenter) eventBus.addHandler(ReceiverPresenter.class, false);
presenter.setGame(Game.Action);
presenter.bind();
.....

Так осуществляется обработка событий в Receivers презентерах и проверка выбранной группы:
public void onSetSelectedItem(String genre, String value) {
        if (isThisGenre(genre)) {
            // show some game
        }
}
Пакет презентеров представлен след. классами:
public abstract class AbstractGroupPresenter<V, E extends EventBus> extends BasePresenter<V, E> {
    private String game;

    public String getGame() {
        return game;
    }

    public void setGame(String game) {
        this.game = game;
    }

    protected boolean isThisGenre(String gameCheck) {
        if (gameCheck == null || gameCheck.equals("")) {
            return false;
        } else {
            return gameCheck.equals(game);
        }
    }
}


* This source code was highlighted with Source Code Highlighter.
@Presenter(view = MainWidget.class)
public class MainPresenter extends BasePresenter<IMainWidget, AppEventBus> {

    public interface IMainWidget {
        public void addSenderWidget(IsWidget widget);
        public void addReceiverWidget(IsWidget widget);
    }

    public void onShowSenderWidget(IsWidget widget) {
        view.addSenderWidget(widget);
    }

    public void onShowReceiverWidget(IsWidget widget) {
        view.addReceiverWidget(widget);
    }
}


* This source code was highlighted with Source Code Highlighter.
@Presenter(view = SenderWidget.class, multiple = true)
public class SenderPresenter extends AbstractGroupPresenter<ISenderWidget, AppEventBus> {
    public interface ISenderWidget {
        void showGroup(String group);
        HasChangeHandlers getSelectableComponent();
        String getSelectedValue();
        void displayValues(List<Genre> asList);
    }

    public void bind() {
        view.showGroup(getGame());
        view.getSelectableComponent().addChangeHandler(new ChangeHandler() {
            @Override
            public void onChange(ChangeEvent event) {
                eventBus.setSelectedItem(getGame(), view.getSelectedValue());
            }
        });
        view.displayValues(Game.valueOf(getGame()).getGenre());
    }
}


* This source code was highlighted with Source Code Highlighter.
@Presenter(view = ReceiverWidget.class, multiple = true)
public class ReceiverPresenter extends AbstractGroupPresenter<IReceiverWidget, AppEventBus> {

    public interface IReceiverWidget {
        void showGroup(String group);
        void setReceivedValue(String value);
    }

    @Override
    public void bind() {
        view.showGroup(getGame());
    }

    public void onSetSelectedItem(String genre, String value) {
        if (isThisGenre(genre)) {
            List<String> games = Genre.valueOf(value).getGames();
            view.setReceivedValue(games.get(Random.nextInt(games.size())));
        }
    }
}


* This source code was highlighted with Source Code Highlighter.
Для хранения типа игр, жанров и названий используется enum Game и Genre.
public enum Game {
    Action(Arrays.asList(Genre.FirstPersonShooter, Genre.ThirdPersonShooter)),
    RPG(Arrays.asList(Genre.ActionRPG, Genre.MMORPG, Genre.TacticalRPG)),
    Strategy(Arrays.asList(Genre.TurnBasedStrategy, Genre.RealTimeTactics));

    private List<Genre> genre;

    private Game(List<Genre> genre) {
        this.genre = genre;
    }

    public List<Genre> getGenre() {
        return genre;
    }
}


* This source code was highlighted with Source Code Highlighter.
public enum Genre {
    /** Action game */
    FirstPersonShooter(Arrays.asList("Call of Duty", "Team Fortress",
            "Halo", "Killzone", "Unreal Tournament", "Doom")),
    ThirdPersonShooter(Arrays.asList("Prince of Persia", "Max Payne",
            "Gears of War", "Resident Evil ", "Army of Two", "Kill Switch")),

    /** RPG game */
    ActionRPG(Arrays.asList("Diablo", "Dungeon Siege",
            "Sacred", "Hellgate: London", "Torchlight", "Mass Effect")),
    MMORPG(Arrays.asList("Final Fantasy", "World of Warcraft",
            "Meridian 59", "Ultima Online", "EverQuest", "The Monster")),
    TacticalRPG(Arrays.asList("Silent Storm", "Fallout", "Freedom Force",
            " Dungeons and Dragons Tactics", "Rebelstar: Tactical Command", "Gladius")),

    /** Strategy game */
    TurnBasedStrategy(Arrays.asList("Civilization", "Heroes of Might and Magic",
            "Making History", "Advance Wars", "Master of Orion")),
    RealTimeTactics(Arrays.asList("Warcraft", "Age of Empires",
            "Dawn of War", "Command and Conquer", "World In Conflict", "Halo Wars"));

    private List<String> games;

    private Genre(List<String> games) {
        this.games = games;
    }

    public List<String> getGames() {
        return games;
    }
}


* This source code was highlighted with Source Code Highlighter.
     Создадим PresenterHandler, который будет отвечать за инициализацию приложения. Метод onInit() вызывается на старте и заполняется виджетами, которые будет отправлять и слушать изменения.
@EventHandler
public class PresenterHandler extends BaseEventHandler<AppEventBus> {
    public void onInit() {
        addWidget(SenderPresenter.class, Game.Action);
        addWidget(ReceiverPresenter.class, Game.Action);
        addWidget(ReceiverPresenter.class, Game.Action);
        addWidget(ReceiverPresenter.class, Game.Action);

        addWidget(SenderPresenter.class, Game.RPG);
        addWidget(ReceiverPresenter.class, Game.RPG);
        addWidget(ReceiverPresenter.class, Game.RPG);
        addWidget(ReceiverPresenter.class, Game.RPG);

        addWidget(SenderPresenter.class, Game.Strategy);
        addWidget(ReceiverPresenter.class, Game.Strategy);
        addWidget(ReceiverPresenter.class, Game.Strategy);
        addWidget(ReceiverPresenter.class, Game.Strategy);
    }

    private void addWidget(
            Class<? extends AbstractGroupPresenter<?, ?>> component, Game game) {
        AbstractGroupPresenter<?, ?> presenter = (AbstractGroupPresenter<?, ?>) eventBus
                .addHandler(component, false);
        presenter.setGame(game.toString());
        presenter.bind();
        if (presenter instanceof SenderPresenter) {
            eventBus.showSenderWidget((Widget) presenter.getView());
        } else {
            eventBus.showReceiverWidget((Widget) presenter.getView());
        }
    }
}


* This source code was highlighted with Source Code Highlighter.
Вот так выглядит запущенное приложение:


Eclipse проект можно скачать по следующей ссылке.

понедельник, 8 августа 2011 г.

Использование JSON в GWT

     JSON (JavaScript Object Notation) - это универсальный, легкий и удобный текстовый формат обмена данными, основанный на языке JavaScript. Как и XML, он очень легко воспринимается людьми. JSON является независимым от языка обработки, существуют специальные библиотеки практически во всех языках программирования. В то время, как XML часто используется, например, для хранения настроек программ, то JSON больше используется для работы в сети. 
     Рассмотрим использование JSON в GWT приложении. В GWT наиболее общий путь взаимодействия с сервером происходит через GWT-RPC, так как он делает все связи на Java особенно простыми и эффективными. JSON используется когда общение с сервером происходит через простые HTTP вызовы. 
     Создадим веб проект в Eclipse (File - New - Web Application Project) и назовем его “JsonGwtApp”. Для работы с JSON на клиентской стороне, в файл JsonGwtApp.gwt.xml следует добавить следующую строку:
<inherits name="com.google.gwt.json.JSON" />
Создадим модель объекта, который будет использоваться для хранения данных.
package com.dmitrynikol.json.gwt.server.model;

import java.util.LinkedList;
import java.util.List;

public class Book {

  public String id;
  public String title;
  public String author;
  public List<Double> prices = new LinkedList<Double>();

  public Book(String id, String title, String author, List<Double> prices) {
    this.id = id;
    this.title = title;
    this.author = author;
    this.prices = prices;
  }
  public String getId() {
    return id;
  }
  public void setId(String id) {
    this.id = id;
  }
  public String getTitle() {
    return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }
  public String getAuthor() {
    return author;
  }
  public void setAuthor(String author) {
    this.author = author;
  }
  public List<Double> getPrices() {
    return prices;
  }
  public void setPrices(List<Double> prices) {
    this.prices = prices;
  }
}
     Теперь пришла очередь к серверной части приложения. Создадим сервлет, который будет использоваться для имитации сервера, от которого мы будет получать данные. Сервлет создает JSON ответ клиенту используя список книг. Также следует добавить в classpath проекта библиотеку json.jar и скопировать ее в “war\WEB-INF\lib”.
package com.dmitrynikol.json.gwt.server;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.dmitrynikol.json.gwt.server.model.Book;
import org.json.JSONObject;

public class BooksServlet extends HttpServlet {

  private static final long serialVersionUID = 5984818046261512114L;

  private static List<Book> books = new LinkedList<Book>();

  static {
    books.add(new Book("132529", "The Hound of the Baskervilles",
        "Arthur Conan Doyle", Arrays.asList(40.0, 45.2)));
    books.add(new Book("745353", "The Man in the Brown Suit",
        "Agatha Christie", Arrays.asList(37.0, 40.3)));
    books.add(new Book("248941", "Liberty Bar", "Georges Simenon",
        Arrays.asList(34.2, 37.7)));
    books.add(new Book("478134", "The Case of the Turning Tide",
        "Erle Stanley Gardner", Arrays.asList(25.5, 28.3)));
    books.add(new Book("328473", "The Dogs of War", "Frederick Forsyth",
        Arrays.asList(25.0, 30.3)));
  }

  @Override
  protected void doGet(HttpServletRequest request,
      HttpServletResponse response) throws ServletException, IOException {
    try {
      JSONObject responseObject = new JSONObject();
      List<JSONObject> booksObjects = new LinkedList<JSONObject>();
   
      for (Book book : books) {
        JSONObject booksObj = new JSONObject();

        booksObj.put("id", book.getId());
        booksObj.put("title", book.getTitle());
        booksObj.put("author", book.getAuthor());
     
        List<JSONObject> pricesObjects = new LinkedList<JSONObject>();
        for (Double price : book.getPrices()) {
          JSONObject priceObj = new JSONObject();
          priceObj.put("price", price);
          pricesObjects.add(priceObj);
        }
     
        booksObj.put("prices", pricesObjects);
     
        booksObjects.add(booksObj);
      }
   
      responseObject.put("books", booksObjects);
   
      PrintWriter writer = response.getWriter();
      writer.write(responseObject.toString());
      writer.flush();
    } catch (Exception ex) {
      ex.printStackTrace();
      throw new ServletException();
    }
  }
}

   Сервлет нам будет возвращать след. данные:
{
 "books": [
  {
   "id": "132529",
   "author": "Arthur Conan Doyle",
   "title": "The Hound of the Baskervilles",
   "prices": [
    {
     "price": 40
    },
    {
     "price": 45.2
    }
   ]
  } … и т.д.
 ]
}
     Сконфигурируем сервлет так, чтобы он отвечал на определенный url. Нужно поправить файл web.xml (war\WEB-INF\web.xml). Он должен выглядеть так:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <servlet>
    <servlet-name>BooksServlet</servlet-name>
    <servlet-class>com.dmitrynikol.json.gwt.server.BooksServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>BooksServlet </servlet-name>
    <url-pattern>/jsongwtapp/books</url-pattern>
  </servlet-mapping>

  <welcome-file-list>
    <welcome-file>JsonGwtApp.html</welcome-file>
  </welcome-file-list>
</web-app>
     Осталось еще написать клиентскую часть. Создадим стартовую точку GWT приложения. На клиентской стороне последовательность действий выглядит так: выполняется HTTP GET вызов у сервера, приходит ответ в JSON формате и выполняется парсинг ответа.
package com.dmitrynikol.json.gwt.client;

import java.util.LinkedList;
import java.util.List;

import com.dmitrynikol.json.gwt.client.widget.BooksWidget;
import com.dmitrynikol.json.gwt.client.widget.IBook;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;

public class JsonGwtApp implements EntryPoint {

  @Override
  public void onModuleLoad() {
    final Button tableBooksButton = new Button("Show books");

    RootPanel.get("showTableBooksButton").add(tableBooksButton);

    tableBooksButton.addClickHandler(new ClickHandler() {
      @Override
      public void onClick(ClickEvent event) {
        getDataFromServer();
      }
    });
  }

  public void getDataFromServer() {
    try {
      RequestBuilder requestBuilder = new RequestBuilder(
          RequestBuilder.GET, "/jsongwtapp/books");

      requestBuilder.setCallback(new RequestCallback() {
        @Override
        public void onResponseReceived(Request request,
            Response response) {
          BooksWidget<IBook> booksWidget = new BooksWidget<IBook>();
          List<IBook> books = parseJsonData(response.getText());
          if (books != null) {
            booksWidget.setData(books);
            booksWidget.show();
          }
        }

        @Override
        public void onError(Request request, Throwable exception) {
          Window.alert(exception.getMessage());
        }
      });
      requestBuilder.send();
    } catch (RequestException ex) {
      Window.alert(ex.getMessage());
    }
  }

  public List<IBook> parseJsonData(String json) {
    JSONValue value = JSONParser.parseStrict(json);
    JSONObject booksObject = value.isObject();
    JSONArray booksArray = booksObject.get("books").isArray();

    if (booksArray != null) {
      List<IBook> books = new LinkedList<IBook>();
      for (int i = 0; i <= booksArray.size() - 1; i++) {
        JSONObject bookObj = booksArray.get(i).isObject();

        String id = bookObj.get("id").isString().stringValue();
        String title = bookObj.get("title").isString().stringValue();
        String author = bookObj.get("author").isString().stringValue();

        JSONArray pricesArray = bookObj.get("prices").isArray();
        List<Double> prices = new LinkedList<Double>();
        if (pricesArray != null) {
          for (int k = 0; k <= pricesArray.size() - 1; k++) {
            JSONObject priceObj = pricesArray.get(k).isObject();
            double price = priceObj.get("price").isNumber().doubleValue();
            prices.add(price);
          }
        }

        IBook book = new IBook(id, title, author, prices);
        books.add(book);
      }
      return books;
    }
    return null;
  }
}
     Для HTTP вызова используется класс RequestBuilder и ассинхронный метод обратного вызова класса RequestCallback. Далее просто вызываем метод send() для отправки HTTP запроса основанного на текущей конфигурации. Информацию о книгах выведем в таблицу, а саму таблицу поместим в диалоговое окно.
package com.dmitrynikol.json.gwt.client.widget;

import java.util.LinkedList;
import java.util.List;

public class IBook {

  public String id;
  public String title;
  public String author;
  public List<Double> prices = new LinkedList<Double>();

  public IBook(String id, String title, String author, List<Double> prices) {
    this.id = id;
    this.title = title;
    this.author = author;
    this.prices = prices;
  }
  public String getId() {
    return id;
  }
  public String getTitle() {
    return title;
  }
  public String getAuthor() {
    return author;
  }
  public List<Double> getPrices() {
    return prices;
  }
  public void setPrices(List<Double> prices) {
    this.prices = prices;
  }
}
package com.dmitrynikol.json.gwt.client.widget;

import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.Widget;

public class BooksWidget<T extends IBook> extends DialogBox {
  public static BooksWidgetUiBinder uiBinder = GWT.create(BooksWidgetUiBinder.class);

  public interface BooksWidgetUiBinder extends UiBinder<Widget, BooksWidget<?>> {
  }

  @UiField CellTable<T> bookTable;
  @UiField Anchor close;

  public BooksWidget() {
    setWidget(uiBinder.createAndBindUi(this));
    setText("Detective books");
  }

  public void setData(List<T> books) {
    bookTable.addColumn(new TextColumn<T>() {
      @Override
      public String getValue(T object) {
        return object.getId();
      }
    }, "Id");
    bookTable.addColumn(new TextColumn<T>() {
      @Override
      public String getValue(T object) {
        return object.getTitle();
      }
    }, "Title");
    bookTable.addColumn(new TextColumn<T>() {
      @Override
      public String getValue(T object) {
        return object.getAuthor();
      }
    }, "Author");
    bookTable.addColumn(new TextColumn<T>() {
      @Override
      public String getValue(T object) {
        List<Double> prices = object.getPrices();
        StringBuffer priceBuf = new StringBuffer();
        for (Double price : prices) {
          priceBuf.append(price + " ");
        }
        return priceBuf.toString();
      }
    }, "Price");
 
    bookTable.setRowCount(books.size(), true);
    bookTable.setRowData(0, books);
  }

  @UiHandler("close")
  void onCloseClicked(ClickEvent event) {
    hide();
  }
}
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"><ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
  xmlns:g="urn:import:com.google.gwt.user.client.ui"
  xmlns:c='urn:import:com.google.gwt.user.cellview.client'
>

  
<g:HTMLPanel>
    <div
>
      
<c:CellTable ui:field="bookTable"/>
    </div
>
    
<div>
      
<g:Anchor ui:field="close" text="close"/>
    </div
>
  </g:HTMLPanel
>
</ui:UiBinder
>

Также следует изменить файл JsonGwtApp.html, т.к. мы обращаемся к елементу по id.
.....
 
<h1 align="center">JSON GWT Application</h1
>
 
<div align="center" id="showTableBooksButton" /
>
.....
     После запуска приложения, на страничке браузера можно увидеть заголовок и кнопку для вывода информации о книгах. После клика на кнопке получаем JSON данные, выполняется парсинг и данные выводятся на экран. 


     Вот и все. Eclipse проект можно скачать по следующей ссылке.