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)
- from : http://www.gwtproject.org/doc/latest/DevGuideMvpActivitiesAndPlaces.html
- sample source : https://code.google.com/p/google-web-toolkit/downloads/detail?name=Tutorial-hellomvp-2.1.zip
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 는 위에서 본 것처럼
- View
- EventBus
- 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 로 이동하는 경우에
goTo() 함수에서는
ClientFactory 에서 PlaceController 를 가져오고, 이 PlaceController 의 goTo() 를 호출해서 새로운 Place로 간다. 이 때 PlaceController 를 아래의 일을 차례대로 수행한다.
- ActivityManager 에게 알려서 current Activity 를 멈추도록 한다.
- 그리고 새로운 Place 와 관련된 Activity 를 찾고 시작한다.
- 그리고 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 해주게 된다.- ClientFactory -> EventBus, PlaceController
- ActivityMapper, ActivityManager
- 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()
댓글 없음:
댓글 쓰기