3월 25일 안드로이드 컨퍼런스가 열립니다. 드로이드 나이츠 정보 보러가기

Android MVP 무작정 따라하기 4번째 입니다.

이전 시간에 Presenter를 총 3가지 방법으로 분리하는 방법에 대해서 정리하였습니다.

  • Google Architecture에서 정의하는 Contract 정의하는 방법
  • Presenter/PresenterImpl로 구분하는 방법
  • View에 대한 interface만 정의하는 방법

선호에 따라서 사용하는 방법은 서로다를 수 있습니다.

저는 구글 Architecture에서 정의하고 있는 Contract를 정의하는 방법을 택하여 사용하고 있습니다.

그래서 오늘은 무작정 따라하기 4번째로 MVC로 작성한 예제 코드를 MVP의 Presenter 분리하는 방법을 영상으로 만들어보았습니다.

샘플코드


영상을 보기 전에

MVC의 예제에서 다음의 코드들을 Presenter로 분리하였습니다.

MainActivity의 코드 중 setImageItems의 모델 사용하는 부분을 Presenter로 분리

// Adapter를 생성
imageAdapter = new ImageAdapter(this);
// Adapter에 itemList를 data를 통해서 불러와서 저장
imageAdapter.setImageItems(SampleImageData.getInstance().getImages(this, 10));
// RecyclerView에 adapter를 세팅
recyclerView.setAdapter(imageAdapter);

MainActivity의 오른쪽 상단 버튼인 reload 발생할 경우의 코드에 대해서 Presenter로 분리

// reload 액션이 발생
if (id == R.id.action_reload) {
    // 기존 itemList clear
    imageAdapter.clear();
    // 새로운 ImageList 불러와서 교체
    imageAdapter.setImageItems(SampleImageData.getInstance().getImages(this, 10));
    // UI Change
    imageAdapter.notifyDataSetChanged();
    return true;
}


영상


Java 주요 코드

주요코드 - MainContract 정의.java

ViewPresenter에 대한 interface를 정의합니다.

public interface MainContract {

    interface View {

        void addItems(ArrayList<ImageItem> items, boolean isClear);

        void notifyAdapter();
    }

    interface Presenter {

        void attachView(View view);

        void detachView();

        void setSampleImageData(SampleImageData sampleImageData);

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

주요코드 - MainPresenter.java

MainPresenter를 다음과 같이 정의합니다.

ViewSampleImageData(모델)을 다음과 같이 셋팅합니다.

private MainContract.View view;

private SampleImageData sampleImageData;

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

@Override
public void setSampleImageData(SampleImageData sampleImageData) {
    this.sampleImageData = sampleImageData;
}

그리고 loadItems을 다음과 같이 정의하였습니다.

@Override
public void loadItems(Context context, boolean isClear) {
		ArrayList<ImageItem> items = sampleImageData.getImages(context, 10);
		view.addItems(items, isClear);
		view.notifyAdapter();
}

주요코드 - MainActivity.java

presenter을 생성하고, loadItems를 호출하게 됩니다.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 생략

    mainPresenter = new MainPresenter();
    mainPresenter.attachView(this);
    mainPresenter.setSampleImageData(SampleImageData.getInstance());

		// 생략
    mainPresenter.loadItems(this, false);
}

UI 갱신을 위한 notifyAdapteraddItems을 아래와 같이 작성합니다.

@Override
public void addItems(ArrayList<ImageItem> items, boolean isClear) {
	 if (isClear) {
			 imageAdapter.clear();
	 }
	 imageAdapter.setImageItems(items);
}

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


Kotlin의 주요 코드

주요코드 - MainContract 정의.kt

kotlin에서 사용가능한 interface 정의를 다음과 같이 합니다.

var viewvar ImageData를 정의합니다.

interface MainContract {
    interface View {

        fun updateItems(items: ArrayList<ImageItem>, isClear: Boolean)

        fun notifyAdapter()
    }

    interface Presenter {
        var view: View
        var imageData: ImageData

        fun loadItems(context: Context, isClear: Boolean)
    }
}

주요코드 - MainPresenter 정의.kt

그리고 lateinit을 통해서 변수를 선언합니다.

java에서는 이때 setView/getView가 자동으로 생성됩니다.

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

그리고 loadItems을 아래와 같이 생성합니다.

updateItems과 notifyAdapter을 각각 호출해주어 UI를 갱신합니다.

override fun loadItems(context: Context, isClear: Boolean) {
    imageData.getSampleList(context, 10).let {
        view.updateItems(it, isClear)
        view.notifyAdapter()
    }
}

주요코드 - MainActivity 정의.kt

private lateinit var presenter: MainPresenter

override fun onCreate(savedInstanceState: Bundle?) {
    // 생략

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

    // 생략

    presenter.loadItems(this, false)
}

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

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에 대한 테스트도 가능합니다.

그래서 다음 글에서는 Adapter에 대한 View와 Model을 한번 분리해보도록 하겠습니다.


MVP 무작정 따라하기

무작정 따라 하기는 MVP 패턴을 이해할 수 있도록 하나씩 차근차근 작성해보는 예제입니다.

그래서 각각의 브런치와 글들을 통해서 조금씩 확장해가려고 합니다.


Tae-hwan

Android, Kotlin .. Create a content development.