4. 4
public class StoreActivity extends Activity …. {
private Handler handler = new Handler();
protected void onStart() {
loadingProgress.show();
LoadListThread lThread = new LoadListThread();
lThread.start();
}
private class LoadListThread extends Thread {
public void run() {
URL url = new URL(urlstr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
...
handler.post(new Runnable() {
public void run() {
loadingProgress.hide();
if (loadingResult) {
localDB.save(loadedData);
drawMarkerForStores(loadedData);
} else {
toasts..short("에러!");
}
}
});
}
}
기능 흐름 제어
데이터 보관 처리
UI 화면 제어
네트
워크
UI 처리 위한 쓰레드
화면 제어
쓰레드 제어
5. 5
public class StoreActivity extends Activity …. {
private MapPoint center;
public void onMapViewDragEnded(
MapView mapView, MapPoint mapPoint) {
center = mapPoint;
loadingProgress.show();
LoadListThread lThread = new LoadListThread();
lThread.start();
}
private class LoadListThread extends Thread {
public void run() {
URL url = new URL(urlstr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
...
if (center != null) { params.put("center", toCoord(center)); }
...
handler.post(new Runnable() {
public void run() {
loadingProgress.hide();
…
}
});
}
}
사용자 이벤트 처리
UI 의존
데이터
화면 제어 + 쓰레드 제어
6. 6
public class StoreActivity extends Activity …. {
private LocalDB localDB;
public void onMarkerTab(MapView mapView, Marker marker) {
StoreData storeData = localDB.select(marker.getMeta().getId());
if (storeData == null) {
alerts.showAlert("데이터가 없습니다.");
} else {
showBalloon(mapView, storeData);
}
}
private void showBallon(MapView mapView, StoreData storeData) {
BalloonInfoWindow infoWin = new BallonInfoWindow(this,storeData);
mapView.addFigure(infoWin);
…
}
데이터
조회
화면 흐름 제어
위젯
제어
12. Model-View-Presenter
12
모델프리젠터뷰
• 비즈니스 로직과
어플리케이션
데이터
• 예, 인증
• 예, 특정 위치 기준
가계 정보
• 이벤트를 프리젠터에
전달
• 예, 로그인 버튼 클릭을
클릭하면 프리젠터에
인증 실행 요청
• 사용자에게 알맞은 화면
제공
• 예, 프리젠터를 통해
전달받은 데이터를
리스트와 같은 위젯을
이용해서 표현
• 로딩 중, 경고 대화창 등
안내
• 사용자 요청에 반응
• 예, 뷰를 통해 인증 요청을
받으면 모델을 사용해서
인증을 수행하고, 결과를
뷰에 전달
• 뷰의 흐름 제어
• 예, 인증 시작 전에 뷰를
통해 진행 중 대화창을
보여주고, 인증에 성공하면
뷰를 통해 메인 화면으로
이동
13. 뷰
• 인터페이스
– 사용자에게 보여질
기능을 정의
– 프리젠터가 호출할 기능
• 예
– 데이터 출력 기능
– 진행 중 알림 기능
• 뷰 구현체
– 인터페이스에 정의한
기능의 구현 제공
– 사용자 이벤트를
프리젠터에 전달
• 예
– 데이터를 리스트로 표시
– 사용자가 항목을
클릭하면 프리젠터에
선택 요청 전달
13
14. 뷰 구현
14
public interface StoreListView {
void showLoadingMessage();
void hideLoadingMessage();
void displayStores(stores);
void showFailMessage();
}
public class MapStoreListView implements StoreListView, … {
private StoreListPresenter presenter;
public void MapStoreListView(StoreListPresenter presenter) {
presenter.setView(this);
}
public void onMapViewDragEnded(
MapView mapView, MapPoint mapPoint) {
Coord center = toCoord(mapView.getCenter()))
presenter.onLocationChange(center);
}
public void showLoadingMessage() {
progress = ProgressDialog.show(this, "", "로딩중");
}
public void hideLoadingMessage() { progress.hide(); }
public void displayStores(List<Store> stores) {
…지도위에 마커 표시
}
15. 프리젠터 정의
• 사용자의 UI 행위를 추상화한 메서드로
기능을 정의
– 뷰 구현에 의존하지 않음
15
onLocationChange: 상점을 조회할 위치 기준을 변경한다는 의미, O
뷰 구현 기술에 종속되지 않음
onMapDragged: 뷰의 지도를 드래그했다는 것을 의미, X
뷰 구현 기술에 종속됨
16. 프리젠터 구현
• 모델과 뷰를 사용해서 사용자의 요청 처리
16
public class StoreListPresenter {
private StoreListView view;
private StoreListModel model;
public void onLocationChange(Coord location) {
view.showLoadingMessage();
try {
List<Store> stores = model.getStoresIn(location, 100);
view.hideLoadingMessage();
view.displayStores(stores);
} catch(Exception e) {
view.hideLoading();
view.showFailMessage();
}
}
UI 로직을
제어
모델로
비즈니스
로직 실행
18. MVP 적용 결과
• 뷰 구현 기술과 UI 로직(프리젠터)의 분리
– UI 로직 응집도 향상
18
19. MVP 적용 결과
• UI 구현 기술(뷰)과 UI 로직(프리젠터)의 분리
– UI 로직에 영향을 주지 않고 뷰 구현 변경 가능
19
public class MapStoreListView
implements StoreListView, … {
private StoreListPresenter presenter;
…
public void displayStores(List<Store> stores) {
listAdapter.clear();
listAdapter.addAll(stores);
}
public class MapStoreListView
implements StoreListView, … {
private StoreListPresenter presenter;
…
public void displayStores(List<Store> stores) {
map.clearAllMarks();
for(Store store : stores) {
map.addMarker(createMarker(store));
}
}
20. MVP 적용 결과
• UI 구현 기술(뷰), UI 로직(프리젠터), 도메인
로직(모델) 구현의 분리
– 뷰/모델 구현없이 UI 로직 테스트 가능
• 뷰를 구현하지 않아도 모의(mock) 객체로 UI 로직에
대한 테스트 가능
20
23. Activity나 Fragment 역할
23
public class MapStoreListActivity implements StoreListView, … {
private StoreListPresenter presenter;
public void onCreate(Bundle savedInstanceState) {
StoreListModel model = new RestStoreListModel();
this.presenter = new StoreListPresenter(this, model);
this.presenter.init();
}
public void onMapViewDragEnded(MapView mapView, MapPoint mapPoint) {
Coord center = toCoord(mapView.getCenter()))
presenter.onLocationChange(center);
}
public void showLoadingMessage() {
progress = ProgressDialog.show(this, "", "로딩중");
}
public void hideLoadingMessage() {
progress.hide();
}
public void displayStores(List<Store> stores) {
…지도위에 마커 표시
}
뷰 구현
프리젠터/모델 객체를
생성하고 연결
24. 안드로이드 앱에서 모델의 특징
• 외부 서버와 통신을 비동기로 처리
– 허니콤(3.0): Main(UI) 쓰레드에서 네트워크 금지
– AsyncTask나 Thread 사용
• 프리젠터에 비동기 결과 전달 방법 필요
– 주요 방법: 콜백(Callback), 옵저버(Observer) 등
24
25. 모델과 콜백
25
public class RestStoreListModel
implements StoreListModel {
public void getStoresIn(Coord center,
int distance,
StoreListCallback callback) {
new Thread(new Runnable() {
public void run() {
try {
… network 처리, 데이터 변환
callback.onSuccess(results);
} catch(Exception ex) {
callback.onFail(ex);
}
}
}).start();
}
public interface StoreListCallback {
void onSuccess(List<Store> stores);
void onFail(Exception ex);
}
26. 프리젠터는 콜백을 통해 결과 수신
26
public class StoreListPresenter {
public void onLocationChange(Coord coord) {
view.showProgressMessage();
model.getStoresIn(coord, 100,
new StoreListCallback() {
public void onSuccess(List<Store> stores) {
view.hideProgressMessage();
view.displayStores(stores);
}
public void onFail(Exception ex) {
view.hideProgressMessage();
view.showFailMessage(ex);
}
});
}
27. 프리젠터와 라이프사이클
• Activity/Fragment의 라이프사이클에 맞춰
기능을 구현해야 할 경우, 프리젠터에
라이프사이클 관련 메서드 정의
27
public class StoreListPresenter {
public void onResume() { … }
public void onStop() { … }
…
}
public class StoreListActivity … {
@Override
protected void onResume() {
super.onResume();
presenter.onResume();
}
@Override
protected void onStop() {
super.onStop();
presenter.onStop();
}
28. 유용한 도구
• Dagger
– 의존 주입(DI)
– Activity에서 직접 프리젠터나 뷰를 생성하지
않도록 해 줌
• RxJava
– 옵저버
– 콜백 인터페이스 대체
28
34. 초기화 예
34
<script>
$(document).ready(function() {
var model = new MeasureListModel();
var view = new MeasureListView();
var presenter = new MeasureListPresenter(view, model);
view.setPresenter(presenter);
presenter.reloadList();
});
</script>
36. 뷰와 외부 입력
• 뷰의 역할: 외부 데이터 입력
– 기능 예
• 블루투스로 데이터를 받는 기능
• GPS로 현재 좌표 받는 기능
– 이 두 기능은 뷰를 통해 전달받는 사용자 이벤트
36
37. 모델 영역의 구조
• 모델에 얼마나 많은 도메인 로직이 있나?
– 앱/웹에서 모델은 주로 다음의 두 가지 수행
• 서버와 통신
– 중요 로직은 서버에 존재
• 임시로 데이터 보관
– 로컬DB, 메모리 등에 보관
• 모델에 다양한 로직이 있다면 테스트 가능한
구조로 만드는 것이 중요
37
38. 프리젠터
• 인터페이스와 구현체로 구분 필요 여부
– 코드 수준 테스트 대상은 주로 프리젠터와 모델
– 프리젠터가 유연해야 하는 경우 드뭄
– 모델 테스트시 프리젠터에 대한 모의 객체가
필요한 경우 없음
• 프리젠터 메서드 이름, 파라미터, 리턴 타입
– 뷰에 의존하면 안 됨
• 프리젠터 먼저 구현하기
38