pt>

czwartek, 9 lutego 2012

GUAVA - integrujemy z GWT i MVP4G

    W poprzednim poście przedstawiłem sposób na szybkie i efektywne tworzenie aplikacji webowych dzięki tandemowi GWT i MVP4G. Jednak arsenał wspomagający jest o wiele większy. Kolejną rzeczą, którą chętnie by widział developer są generyczne operacje na kolekcjach. Parę miesięcy temu miałem okazję skorzystać z LambdaJ przy okazji poprzedniego projektu, chciałem skorzystać z tej biblioteki dla celów obecnego projektu opartego właśnie na GWT i MVP4G (oraz MyBatis). Niestety, spotkało mnie przykre rozczarowanie - LambaJ korzysta z aop'a i refleksji, co skutecznie uniemożliwia współpracę z GWT - na co również wskazuje  JRE Emulation Reference. Jakakolwiek próba użycia LambdaJ kończyła się wyjątkami. To zachęciło mnie to do zapoznania się z niejako zamiennikiem dla LambdaJ - z bibliotekami Google GUAVA.
    Potrzebne narzędzia/biblioteki : 
    Zaczynamy zatem! Ze strony głównej projektu ściągamy interesujące nas JAR'y : guava-11.0.1.jar oraz guava-gwt-11.0.1.jar i umieszczamy je w podkatalogu lib. Teraz już posiadamy całą infrastrukturę, kolejnym krokiem jest modyfikacja deskryptora projektu - *.gwt.xml. Dla ułatwienia pokażę cały plik gdyż nie jest on i tak długi, podświetlone są 2 istotne linie kodu :


<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='tutek_mvp4g'>
  <!-- Inherit the core Web Toolkit stuff.                        -->
  <inherits name='com.google.gwt.user.User'/>

  <!-- Inherit the default GWT style sheet.  You can change       -->
  <!-- the theme of your GWT application by uncommenting          -->
  <!-- any one of the following lines.                            -->
  <inherits name='com.google.gwt.user.theme.clean.Clean'/>
  <!-- <inherits name='com.google.gwt.user.theme.standard.Standard'/> -->
  <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
  <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->  
  
  <!-- Other module inherits                                      -->
  <inherits name='com.mvp4g.Mvp4gModule' />
  <inherits name="com.google.common.collect.Collect" /> 
  <inherits name="com.google.common.base.Base"/> 

  <!-- Specify the app entry point class.                         -->
  <!-- <entry-point class='tutek.client.Tutek_MVP4G'/>      -->
  <entry-point class='com.mvp4g.client.Mvp4gEntryPoint'/>

  <!-- Specify the paths for translatable code                    -->
  <source path='client'/>
  <source path='shared'/>

</module>
    Podświetlone linie importują potrzebne moduły biblioteki GUAVA. Kolejnym krokiem jest modyfikacja eventBusa. Dodamy jedno zdarzenie - wybieranie wierszy po przez wartość jednej z kolumn. Definicja interfejsu eventBus nie jest zbyt obszerna więc ponownie zamieszczę cały kod zaznaczając istotne linie :

package tutek.client;

import tutek.client.bean.Person;
import tutek.client.presenter.TutekPresenter;

import com.google.gwt.view.client.ListDataProvider;
import com.mvp4g.client.annotation.Event;
import com.mvp4g.client.annotation.Events;
import com.mvp4g.client.annotation.Start;
import com.mvp4g.client.event.EventBus;


@Events(startPresenter = TutekPresenter.class)
public interface TutekEventBus extends EventBus
{
 
 @Start //obowiązkowe zdarzenie startowe
 @Event(handlers = {TutekPresenter.class})
 public void onStart();
 
 //zdarzenie wybierania osób po imieniu
 @Event(handlers = {TutekPresenter.class})
 public void selectByName(String name, ListDataProvider<Person> data);

}

    Zdarzenie "selectByName" jako parametr przyjmuje imię (name) wg. którego należy przefiltrować zawartość (data) tabelki. Jego obsługa jest zdefiniowana w klasie presentera - pojawi się nowa metoda o nazwie "onSelectByName" - nazwa zgodna z konwencją omówioną w poprzednim wpisie. Bezpośrednio w metodzie obsługującej zdarzenie modyfikujemy listę obiektów do wyświetlenia - przypomina to trochę funkcje statyczne. Klasa reprezentująca dane wyświetlane w tabeli - "Person" oraz klasa widoku ("TutekView") pozostają bez zmian w stosunku do poprzedniego wpisu. Zmienia się natomiast wnętrze klasy presentera :

 @Override
 public void bind()
 {
  GWT.log("bind");
  //tworzymy kolumny
  TextColumn<Person> name = new TextColumn<Person>() 
  {   
   @Override
   public String getValue(Person object)
   {
    return object.name;
   }
  };
  
  TextColumn<person> surName = new TextColumn<Person>() 
  {   
   @Override
   public String getValue(Person object)
   {
    return object.surName;
   }
  };
  
  //dodajemy kolumny
  view.cellTable.addColumn(name);
  view.cellTable.addColumn(surName);
  
  //obsługa zdarzeń
  view.btn_filter.addClickHandler(new ClickHandler() 
  {   
   @Override
   public void onClick(ClickEvent event)
   {   
    eventBus.selectByName("wacek", dataProvider);
   }
  });  
 }
    Zmiany nastąpiły w metodzie bind(). Dokładniej zmianie uległa obsługa zdarzeń - teraz zamiast dokonywać obsługi zdarzenia w kodzie tej metody odpalamy zdarzenie dla eventBusa. Definicja tego zdarzenia zapisana w eventBusie  mówi, że w prezenterze pojawi się nowa metoda, obsługująca zdarzenie będzie się nazywała "onSelectByName", zatem przyjrzujmy się jej :

 public void onSelectByName(final String name, ListDataProvider<Person> data)
 {
  //Obiekt definiujący warunek
  Predicate<Person> nameCond = new Predicate<Person>() 
  {
   //nadpisana metoda szczegułowo definiujaca warunek filtrowania
   @Override
   public boolean apply(Person arg0)
   {
    return arg0.name.toLowerCase().equals(name);
   }   
  };
  data.setList( Lists.newArrayList( filter(data.getList(), nameCond) ) );    
 }

    To co jest ważne podczas filtrowania listy : zdefiniowanie predykatu - czyli warunku (pierwsze podświetlenie) następnie przefiltrowanie. Czujni zauważyli na pewno, że filtrowanie za pomocą funkcji "filter" wygląda tak jak by owa metoda została zdefiniowana w prezenterze - jednak to nie tak. Dzięki statycznemu importowi :

import static com.google.common.collect.Collections2.filter;

    Mozna używać funkcji "filter" z klasy "Collections2" tak jak by była zdefiniowana w naszej klasie. To wszystko - magia działa. Funkcja statyczna "filter" zwraca nową kolekcję, którą podstawiamy dla dataProvidera dostarczonego jako parametr - nie musieliśmy tego robić, jednak w moim odczuciu zwiększa to czytelność kodu. To wszystko.... Chyba nie - gdy wykorzystalem bibliotekę GUAVA w większym projekcie podczas kompilacji GWT (generowanie kodu js) dostalem nieoczekiwanie błędy, co najdziwniejsze, w bibliotece GUAVA.

[ERROR]
 Errors in 'jar:file:/C:/.../lib/guava-gwt-10.0.1.jar!/com/google/common/collect/BstBalancePolicy.java'
         [ERROR] Line 19: The import javax.annotation.Nullable cannot be resolved
         [ERROR] Line 36: Nullable cannot be resolved to a type
         [ERROR] Line 36: Nullable cannot be resolved to a type
         [ERROR] Line 43: Nullable cannot be resolved to a type
         [ERROR] Line 44: Nullable cannot be resolved to a type
         [ERROR] Line 44: Nullable cannot be resolved to a type
    Parę pytań do dr Google dało odpowiedź że brakuje biblioteki jsr305, uzupełniłem. Jednak to nadal nie dało rezultatu i błąd nadal się pojawiał. Dłuższe poszukiwania rozwiązania nie dały rezultatu aż w desperacji zacząłem próbować starszych wersji biblioteki GUAVA. Błąd pojawiał się również podczas używania wersji 10. Kolejna próba z wersją GUAVA 09 dała pozytywny rezulta - kompilacja GWT przebiegała bez błędów. Ciekawym jest fakt, iż błąd ujawniał się wewnątrz biblioteki, dodatkowo podczas zastosowania w większym projekcie. Zastosowanie zestawu GWT + MVP4G daje niezwykłą łatwość i porządek podczas tworzenia aplikacji. Dodanie do tego tandemu bibliotek GUAVA ułatwia manipulacje na danych (nie tylko!!) i dodatkowo zwiększa czytelność kod. Na koniec podaję link do gotowego projektu w Eclipse spakowanego rarem. Brakuje jedynie refleksji. Ale o tym już niebawem!

Brak komentarzy:

Prześlij komentarz