[컴][웹] GWT 사용하기 - MVP 와 Place


MVP and History / MVP with History / GWT Places


이부분의 MVP 와 관련된 자료를 먼저 보고 보는 것이 좋을 듯 하다. MVP 에서 History 를 구현한 부분을 google 에서 API 안으로 흡수해서 Place 를 구현하고 있기 때문이다.

MVP 설명 : http://www.gwtproject.org/articles/mvp-architecture.html


MVP 와 Place(History)





View 와 Activity


MVP 개발에서 view 는 interface 에 의해 정의되고, 이를 이용해서 client 의 특징(모바일, 데스크탑 등)에 따른 다양한 view 를 만들 수 있다.

View 를 위해서 꼭 상속받아야 하는 interface 는 없지만, View 가 실제로 Widget 이라면, GWT 2.1 에 소개된 IsWidget 을 상속받는 것은 유용하므로, IsWidget 을 상속받자.

DOM 명령들이나, view 등을 포함하는 Widget 을 만드는 것은 상대적으로 비용이 많이 든다. 그러므로 재사용을 하는 것이 유리하다.

public interface HelloView extends IsWidget {
    void setName(String helloName);
    void setPresenter(Presenter presenter);

    public interface Presenter {
        void goTo(Place place);
    }
}

Activity 는 android programming 의 activity 와 비슷하다. 즉 하나의 화면이 하나의 activity 라고 생각하면 비슷할 듯 하다. view 는 기존의 UI programming 에서 이야기 하는 widget 같은 개념으로 이해하면 된다.

ClientFactory

Activity 나 Place 를 만들 때 ClientFactory 가 필요하지는 않다.

EventBus, GWT Place Controller, View 구현들을 제공하는 ClientFactory


public interface ClientFactory {
    EventBus getEventBus();
    PlaceController getPlaceController();
    HelloView getHelloView();
    GoodbyeView getGoodbyeView();
}

위의 녀석을 상속받아서 ClientFactoryImpl 을 제공한다.

onModuleLoad() 부분에 ClientFactory 를 GWT.create() 해준다. 이 ClientFactory 를 바꾸는 것으로 간단히 Mobile 용 app 이나 Desktop app 등을 제공할 수 있다.

<moule>.gwt.xml 에 아래같은 부분을 넣어주면, ClientFactory 를 알아서 ClientFactoryImpl 로 바꾸게 된다.


 <!-- Use ClientFactoryImpl by default -->
    <replace-with class="com.hellomvp.client.ClientFactoryImpl">
    <when-type-is class="com.hellomvp.client.ClientFactory"/>
    </replace-with>


ClientFactory 를 new 하면 그 안에서 EventBus 와 PlaceController 를 만든다. 그리고 사용하는 모든 View 들을 만든다.

여기서 만든 view 들을 Activity의 start() 내에서 이용하게 된다.



Activity

AbstractActivity 를 상속해서 사용하면 된다.
View interface 에 대한(implementation 이 아닌) reference 를 가지고 있고,
Activity 는 또한 view의 Presenter interface 를 implement 한다.
view 가 Activity 에 있는 method 를 call 할 수 있게 해준다.

public class HelloActivity extends AbstractActivity implements HelloView.Presenter

...
@Override
public void start(AcceptsOneWidget containerWidget, EventBus eventBus) {
    HelloView helloView = clientFactory.getHelloView();
    helloView.setName(name);
    helloView.setPresenter(this);
    ...



Activity class 에 필요한 모든 동작(method)를 만들어 놓고, 이 activity 를 view 에 setPresenter() 해준다. view 에서는 자신이 필요한 동작을 Presenter 라는 inner interface 에 정의해 놓고, activity 가 이 interface 를 implement 해서 view 에서 필요한 동작을 구현하도록 한다.


Activity.start()

start() 에서 Activity 를 만든다고 생각하면 될 듯 하다. start() 에서는 대부분  view 에서 필요한 controller 등 필요한 것들을 set 하고, 이 view 를 container 에 set 하는 일을 한다. 만약에 request() 같은 일을 한다면 바로 set 할 수 없을 수 있다. 이런 경우는 먼저 private variable 을 set 을 해 놓고, 이 onSuccess 에서 variable 에 set 해주는 방식을 해주면 된다.

View 가 가진 data 의 초기화

참고로, 이 start() 는 MVP 에서 goto(Place) 같이 Place 를 새롭게 호출될 때마다 새롭게 불려진다. 한 번 만들면 저장했다가 다시 사용하는 것이 아니라, 다시 호출될 때마다 다시 만드는 것이라고 생각하면 될 듯 하다. 그러니까 새로운 Activity 를 만들려고 한다면, ClientFactory 에서 만들어서 계속 사용하는 view 같은 경우에는 상황에 따라 data 를 clear 하는 작업이 필요할 수도 있다.

그리고 View 와 관련된 data 도 activity 에서 관리를 하면 계속 초기화 될 것이다. view 가 사라지지 않는한 계속 유지하고 싶다면, view 안에서 data 를 관리해야 할 것이다.



parameter passing

Place 로 Activity 를 호출하면, parameter 를 넘겨주고 싶은 경우가 있다.

이 경우에 AppActivityMapper 에서 new Activity() 를 한 후에 Place 에 맞는 parameter 를 set 해주는 방식을 취하면 된다.

HelloWorld 예제에 나오는 방법인데, 아래 링크를 보면 좀 더 이해하기 좋을 것이다.




HelloWorld example
Activity 의 생성자(constructor) 는 2개의 argument 를 갖는다.
Place
ClientFactory
Place 는 Place 가 가지고 있는 state 의 속성을 얻기 쉽게 해준다.

Factory 로 부터 activity 를 얻을 수도 있다.
Activity 는 쓰고 버리는 생각으로 만들지만, view 는 재사용하는 쪽으로 만든다. view 가 DOM 호출이 필요해서 만드는데 비용이 많이 든다.

ClientFactory 는 위에서 본 것처럼
  1. View
  2. EventBus
  3. PlaceController 
등에 접근하기 쉽게 해준다.



ActivityManager 

override 한 start() 함수는 ActivityManager 에 의해 불려진다.
start() 에서는 view 를 update 하고, 그리고 나서 setWidget() 를 이용해서 view 를 다시 Activity의 container widget 의 view 로 설정한다.

@Override
public void start(AcceptsOneWidget containerWidget, EventBus eventBus) {
    HelloView helloView = clientFactory.getHelloView();
    helloView.setName(name);
    helloView.setPresenter(this);
    containerWidget.setWidget(helloView.asWidget());
}


mayStop() 함수는
  • 화면이 닫혀서 Activity 가 stop 되려는 상황이거나 
  • 다른 Place 로 이동하는 경우에
warning 을 제공한다. warning 을 제거하려면 null 을 return 하면 된다.


goTo() 함수에서는
ClientFactory 에서 PlaceController 를 가져오고, 이 PlaceController 의 goTo() 를 호출해서 새로운 Place로 간다. 이 때 PlaceController 를 아래의 일을 차례대로 수행한다.

  1. ActivityManager 에게 알려서 current Activity 를 멈추도록 한다.
  2. 그리고 새로운 Place 와 관련된 Activity 를 찾고 시작한다.
  3. 그리고 PlaceHistoryHandler 의 URL 을 update 한다.



Places

Place 는 com.google.gwt.place.shared.Place 를 상속받고, PlaceTokenizer 를 가져야 한다.

굳이 state 를 save 하지 않아도 되는 경우라면 BasicPlace 를 상속해서 사용하면 된다. 이 BasicPlace 가 가지고 있는 Tokenizer 를 getToken() 때 null 을 return 한다.


PlaceHistoryMapper

PlaceTokenizer 들과 GWT 의 PlaceHistoryHandler 를 연결해주는 녀석이 PlaceHistoryMapper 이다.

app 에서 쓰이는 모든 Place들을 정의한다. tokenizer class 들을 list 하기 위해서 @WithTokenizers 를 이용한다.

@WithTokenizers({HelloPlace.Tokenizer.class, GoodbyePlace.Tokenizer.class})
public interface AppPlaceHistoryMapper extends PlaceHistoryMapper
{
}

PlaceHistoryHandler 는 browser URL 과 각 Place 를 맞춰준다.(synchronization)

PlaceTokenizer 에 @Prefix annotation 을 사용하면 PlaceHistoryHandler 에게 Place와 연관된 URL 의 첫 부분을 바꾸도록 할 수 있다.


ActivityMapper

Place 와 해당하는 Activity 를 연결시켜주는 녀석이다.
ActivityMapper 를 implement 해야만 한다. 그리고 아래 같은 코드를 많이 가지게 된다.
if(place instanceof SomePlace){
 return SomeActivity(place)
}


Activity 를 만들때 ClientFactory 를 parameter 로 넘겨줘야 하는 경우도 있기 때문에 ActivityMapper 는 ClientFactory의 reference 를 가지고 있어야 한다.


onModuleLoad()

onModuleLoad() 에서 아래의 녀석들을 new 해주게 된다.

  1. ClientFactory -> EventBus, PlaceController
  2. ActivityMapper, ActivityManager
  3. PlaceHistoryMapper, PlaceHistoryHandler

새로운 Place 가 request 되면 ActivityManager 가 PlaceChangeRequestEvent 를 받게된다. 그러면 Activity는 onMayStop() 을 호출하고, 그 결과에 따라 다른 place 로 넘어가는 지가 결정된다. 다음 Place 로 넘어가는 상황이 되면, 현재의 Activity 를 버리고 요청한 Place 에 해당하는 "새로운 녀석"을 시작한다. 이 때 이 "새로운 녀석"을 찾기 위해 ActivityMapper 를 사용하게 된다.


AcitivityManager 와 함께, app 의 Place 들을 추적하기 위해 아래 2개의 다른 GWT class 들이 동작한다.
  • PlaceController : PlaceController 은 새로운 Place 로 navigation 을 시작한다. 이 PlaceController 가 새로운 Place 로 움직이기 전에 onMayStop() 을 호출하는 역할을 한다.(?)
  • PlaceHistoryHandler : PlaceHistoryHandler 는 Place와 URL 사이에 양방향 mapping 을 제공한다. 그래서 Place 로 지정된 URL 을 알수 있고, URL 로 지정된 Place 를 알 수 있다.






public void onModuleLoad()
{
  // Create ClientFactory using deferred binding so we can replace with different
  // impls in gwt.xml
  ClientFactory clientFactory = GWT.create(ClientFactory.class);
  EventBus eventBus = clientFactory.getEventBus();
  PlaceController placeController = clientFactory.getPlaceController();

  // Start ActivityManager for the main widget with our ActivityMapper
  ActivityMapper activityMapper = new AppActivityMapper(clientFactory);
  ActivityManager activityManager = new ActivityManager(activityMapper, eventBus);
  activityManager.setDisplay(appWidget);

  // Start PlaceHistoryHandler with our PlaceHistoryMapper
  AppPlaceHistoryMapper historyMapper= GWT.create(AppPlaceHistoryMapper.class);
  PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapper);
  historyHandler.register(placeController, eventBus, defaultPlace);

  RootPanel.get().add(appWidget);
  // Goes to place represented on URL or default place
  historyHandler.handleCurrentHistory();
}






register()
이 함수안에서 PlaceChangeEvent 가 발생할 때 history.newItem() 부분을 호출하도록 handler 를 event bus 에 추가하고 있으며, 이 newItem() 이 호출하게 될 ValueChangeHandler() 도 등록해 주고 있다. ValueChangeHandler() 에서 handleHistoryToken() 를 호출하는데 이 안에서 placeController.goto() 를 호출한다.


public HandlerRegistration register(PlaceController placeController, EventBus eventBus, Place defaultPlace) 

  eventBus.addHandler(PlaceChangeEvent.TYPE, new PlaceChangeEvent.Handler() {
    public void onPlaceChange(PlaceChangeEvent event) {
      Place newPlace = event.getNewPlace();
      historian.newItem(tokenForPlace(newPlace), false);

  historian.addValueChangeHandler(new ValueChangeHandler() {
    public void onValueChange(ValueChangeEvent event) {
      String token = event.getValue();
      handleHistoryToken(token);
    }
  });

private void handleHistoryToken(String token) {
  ...
  placeController.goTo(newPlace);
}


unregister

이렇게 등록된 event handler 를 unregister 할 때는 Activity.onStop() 을 이용하자. Activity.onStop() 에서 등록된 handler 들을 모두 unregister 해주면 된다.

그래서 unregister 를 위한 HandlerRegistration 은 굳이 view 가 가지고 있을 필요는 없고, Activity 에 남겨두면 된다.

아래 link 에 있는 예제를 참고하자.
참고로, MVP pattern 을 쓴다면, Activity 의 start 의 parameter 로 넘어오는 EventBus 를 사용하자. 그러면 register 에 대한 걱정을 덜 수 있다.






onModuleLoad
  historyHandler.handleCurrentHistory();
    ...
    handleHistoryToken(token)
      newPlace = mapper.getPlace(token);
      ...
      placeController.goTo(newPlace);
        ...
        String warning = maybeGoTo(newPlace);
          if (warning == null || delegate.confirm(warning)) {
            where = newPlace;
            eventBus.fireEvent(new PlaceChangeEvent(newPlace));
          }



PlaceChangeEvent

이 event 는
  • PlaceController.goTo(Place newPlace)
에서 발생 시킨다.
이 PlaceController.goTo() 는 보통 Activity 에 구현된 Presenter.goTo() 함수 안에서 호출된다. 즉 view 에서 사용되어 진다고 생각하면 되겠다.

View.onClick()
  Presenter.goTo()
    PlaceController.goTo()






댓글 없음:

댓글 쓰기