이번에는 참고하는 블로그의 태환님께서 영상 올리신걸 참고하여 Presenter를 분리하는 방법을 배웠다.

이를 기반으로 코틀린 주요 코드를 작성했지만, 현재(2019.01.30) 자바를 이용해 프로젝트를 진행하고 있고, MVP를 적용해야 하기 때문에 한 번 더 공부를 해서 주요 코드를 작성하고자 한다.

Java의 주요 코드

MainContractor 정의

MainContractor는 View와 Presenter에 대한 인터페이스를 정의한다. 이해하는데 용이하게 하기 위한 용도이다.

  • View 인터페이스에는 Presenter로부터 가공된 데이터를 받고 UI를 갱신하는 로직이 필요하다.
  • Presenter 인터페이스에는 View로부터 이벤트를 받고 데이터를 가공하는 로직이 필요하다. 그리고 View로 데이터를 넘겨줘야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface MainContractor{

interface View{
void addItems(ArrayList<ImageItem> list, Boolean isClear);

void notifyAdapter();
}

interface Presenter{
void attachView(View view);

void detachView();

void setSampleImageData(SampleImageData data);

void loadItems(Context context, Boolean isClear);
}
}

MainPresenter 주요 코드

  • Presenter는 View를 들고 있는다. View에게 가공된 데이터를 넘기기 위해서 View를 들고 있는 것이다. 또한, 여기서는 데이터를 세팅하는 과정까지 포함하고 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private MainContractor.View view;
private SampleImageData sampleImageData;

@Override
public void attachView(MainContractor.View view){
this.view = view;
}

@Overrid
public void detachView(){
view = null;
}
@Override
public void setSampleImageData(SampleImageData sampleImageData) {
this.sampleImageData = sampleImageData;
}

MainPresenter의 loadItems 코드

  • 그리고 loadItems는 View에서 Presenter의 loadItems 함수를 호출한다. 그러면 Presenter에서는 이미지 데이터를 가져와서 view의 addItems와 notifyAdapter 함수를 호출함으로써 View에게 update된 데이터를 넘겨준다.
1
2
3
4
5
6
@Override
public void loadItems(Context context, Boolean isClear) {
ArrayList<ImageItem> imageItemList = sampleImageData.getSampleList(context, 10);
view.addItems(imageItemList, isClear);
view.notifyAdapter();
}

MainActivity 주요 코드

  • MainActivity에서는 Presenter를 들고 있다. 그리고 Presenter의 loadItems 함수를 호출한다. 그러면 Presenter에서는 데이터를 가공하고 View에게 전달하는 로직을 가지고 있다. 아래는 loadItems()를 호출하는 부분이다.
1
2
3
4
5
6
mMainPresenter = new MainPresenter();
mMainPresenter.attachView(MainActivity.this);

mMainPresenter.setSampleImageData(SampleImageData.getInstance());

mMainPresenter.loadItems(this, false);

UI 갱신을 위한 주요 코드

  • UI 갱신을 위한 부분은 addItems()와 notifyAdapter() 부분이다. 이 함수는 결국 Presenter에 loadItems()를 호출하고 Presenter가 View에게 데이터를 전달하고 View에서 UI 갱신이 일어나는 과정이다.
1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void addItems(ArrayList<ImageItem> items, boolean isClear) {
if (isClear) {
imageAdapter.clear();
}
imageAdapter.setImageItems(items);
}

@Override
public void notifyAdapter() {
imageAdapter.notifyDataSetChanged();
}

Kotlin의 주요 코드

MainContract 정의
Kotlin에서 사용 가능한 interface 정의를 다음과 같이 한다. var view, var ImageData를 정의한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface MainContract{
interface View{
// View의 UI 갱신을 위한 메소드
fun updateItems(items : ArrayList<ImageItem>, isClear : Boolean)

fun notifyAdapter()
}

interface Presenter{
var view : view
var imageDat : SampleImageData

// Model에 접근하기 위한 메소드
fun loadItems(context : Context, isClear : Boolean)
}
}

MainPresenter 정의
그리고 lateinit을 통해서 변수를 선언한다.
자바에서는 이때 setView/getView가 자동으로 생성된다.

1
2
lateinit override var view: MainContract.View
lateinit override var imageData: ImageData

그리고 loadItems을 아래와 같이 생성한다. updateItems와 notifyAdapter을 각각 호출해주어서 UI를 갱신한다.

1
2
3
4
5
6
override fun loadItems(context: Context, isClear: Boolean) {
imageData.getSampleList(context, 10).let {
view.updateItems(it, isClear)
view.notifyAdapter()
}
}

MainActivity 정의

1
2
3
4
5
6
7
8
9
10
11
privaet lateinit var presenter : MainPresenter

override fun onCreate(savedInstancesState: Bundle?){

presenter = MainPresenter().apply{
view = this@MainActivity
imageData = SampleImageData
}

presenter.loadItems(context, false)
}

그리고 presenter로부터 View에 대한 콜백을 다음과 같이 처리한다.

1
2
3
4
5
6
7
8
9
10
11
12
override fun updateItems(items: ArrayList<ImageItem>, isClear: Boolean) {
imageAdapter?.apply {
if (isClear) {
imageList?.clear()
}
imageList = items
}
}

override fun notifyAdapter() {
imageAdapter?.notifyDataSetChanged()
}

느낀점

MVP로 나아가기 위해서 조금씩 걸어가고 있다. 하지만 아직 어려운 건 사실이다. 익숙해지기 위해서는 더 많이 코드를 짜보고 생각을 해봐야 할 것 같다.

MVC가 보기에는 편하다. 몇 줄 적지 않았던 코드는 길게 늘어났고, 클래스도 많아졌다. 아직 MVP라고 하기엔 거리가 멀지만 단순히 생각해보면 Activity에서 Model을 분리했다?? 라는데 아니라고 보일지도 모른다. 그래도 최소한 View/Model 간의 결합도를 낮출 수 있고, View/Model에 대한 테스트도 가능하다.

참고