Android에서 사용하기 위한 Base 코드 2번째 글입니다.

읽기 전에 미리 읽어주심을 추천합니다.


MVP 적용은?

MVP는 다음과 같이 적용하였습니다.

Activity/Fragment에 대하여 각각 Presenter를 초기화합니다.

PresenterAbstractPresenter를 상속받아서 구현하게 되는데 다음과 같은 함수가 BasePresenter에 포함되어 있습니다.

fun attachView(view: VIEW)
fun detachView()

위의 함수들은 모두 BasePresenterActivityBasePresenterFragment에서 각각 호출하고 있습니다.

그래서 실제 사용할 때는 별다른 작업을 하지 않아도 되도록 작업해두었습니다.

그래서 포함되는 클래스는 MVP를 포함하는 경우와 하지 않는 Activity/Fragment가 구분하도록 수정하였습니다.

  • MVP 기반의 BasePresenterActivity/BasePresenterFragment 기본 베이스
  • MVP를 사용하지 않는 경우의 BaseActivity/BaseFragment

정리하면 MVP를 사용하기 위한 Presenter와 View의 정의를 설명하려고 합니다.

오늘 작성할 부분을 Diagram으로 표현하면 다음과 같습니다.

er-diagram


코틀린 Base 배포

최신 버전의 Kotlin으로 작성한 support base는 다음과 같습니다.

Download

compile 'tech.thdev.support:base:release_version_code'


BasePresenter

BasePresenter에는 attachViewdetachView를 포함하고 있습니다.

전체 코드는 다음과 같습니다.

interface BasePresenter<in VIEW : BaseView> {

    /**
     * View Attach.
     */
    fun attachView(view: VIEW)

    /**
     * View detach
     */
    fun detachView()
}

attachView를 통해서 View를 세팅 받고, detachView를 통해서 view를 null 처리하게 됩니다.


BaseView

BaseView입니다. 사실 이 BaseView는 하는 일이 없습니다. interface를 만들기 위한 목적으로 사용하고 있는데 아직은 설정해주는 부분이 없습니다.

interface BaseView {

}


AbstractPresenter

저는 Presenter를 좀 더 편하게 사용하기 위해서 다음과 같이 정의하였습니다.

AbstractPresenter라는 이름을 사용하고 VIEW를 Generic으로 사용합니다.

여기에서는 VIEW를 초기화하기 위한 attachView와 VIEW를 null 처리하기 위한 detachView를 정의하였습니다.

아래와 같이 정의하고, VIEW는 in/out을 모두 다 하기 때문에 별도의 함수 표기가 존재하지 않습니다.

attachView는 외부에서 접근할 수 없도록 하기 위해서 private set을 추가로 설정하였습니다. 그래서 외부에서는 set을 사용할 수 없고, get 역시 AbstractPresenter를 상속받을 경우에만 사용이 가능합니다.

abstract class AbstractPresenter<VIEW : BaseView> : BasePresenter<VIEW> {

    protected var view: VIEW? = null
        private set

    override fun attachView(view: VIEW) {
        this.view = view
    }

    override fun detachView() {
        view = null
    }
}


Presenter를 위한 interface 정의

저는 Google Architecture를 사용하여 다음과 같은 interface를 정의합니다.

이름에는 Contract를 다음과 같이 만들어서 사용하고 있습니다.

다음과 같은 예를 확인할 수 있는데 MainContract를 정의한 모습입니다.

interface를 ViewPresenter를 모두 정의한 상태입니다.

public interface MainContract {

    interface View extends BaseView {

        void showMessage(String message);
    }

    interface Presenter extends BasePresenter<View> {

        void updateMessage();
    }
}


Presenter를 정의한다

위와 같은 interface를 정의하였으니 이제는 Presenter를 정의해보려고 합니다.

MainPresenter는 다음과 같이 정의할 수 있습니다.

extends에는 AbstractPresenter을 정의하고, implements에는 Presenter를 또 한번 정의합니다… 조금은 귀찮네요. 줄이도록 해봐야겠습니다.

다음만 호출하게 되면 AbstractPresenter에서 구현한게 있으니 그대로 사용하면 됩니다.

public class MainPresenter extends AbstractPresenter<MainContract.View> implements MainContract.Presenter {

    @Override
    public void updateMessage() {
        getView().showMessage("MainPresenter example");
    }
}


BasePresenterActivity/BasePresenterFragment 초기화 시

Presenter를 위와 같이 정의할 수 있는데 저는 Base를 이용하여 BasePresenterActivity/BasePresenterFragment을 한 번 더 정의하였습니다.

좀 더 사용하기 편하게 하기 위해서 FragmentActivity를 다음과 같의 정의해두었습니다.

해당 코드에서 BasePresenter를 상속받아서 정의하였습니다.

BasePresenterFragmentBasePresenterActivity는 Generic으로 VIEWPresenter를 세팅하게 만들었습니다. 그래서 실제 구현하는 곳에서 VIEWPresenterinterface를 세팅해주셔야 합니다. 해당 부분은 아래에서…

다음의 코드에서 Presenter를 초기화할 수 있도록 onCreatePresenter 함수를 별도로 만들었습니다. 강제로 상속받아서 초기화하게 되며, 하지 않으면 null입니다.

Kotlin의 장점인 null처리를 해주고 attachViewthis as VIEW를 그대로 세팅해주면 해당 VIEWPresenter에서 사용하게 됩니다.

onDestroy가 호출되면 자동으로 detachView를 호출하여 null처리합니다.

abstract class BasePresenterFragment<in VIEW : BaseView, P : BasePresenter<VIEW>> : BaseFragment() {

    protected var presenter: P? = null
        private set

    abstract fun onCreatePresenter(): P?

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        presenter = onCreatePresenter()
        presenter?.attachView(this as VIEW)
        return super.onCreateView(inflater, container, savedInstanceState)
    }

    override fun onDestroy() {
        super.onDestroy()

        presenter?.detachView()
    }
}


Presenter를 사용하는 예제

오늘 작성한 Presenter와 View를 사용하게 되면 다음과 같이 사용하게 됩니다.

BasePresenterActivityBasePresenterFragment를 각각 extends로 상속받으면 되고, 다음과 같이 사용합니다.

참고로 Kotlin에서는 Generic을 항상 사용하게 되지만, Java에서는 생략이 가능합니다. 이 경우 Object로 초기화됩니다.

public class MainFragment
        extends BasePresenterFragment<MainContract.View, MainContract.Presenter>
        implements MainContract.View {

        @Nullable
        @Override
        public MainContract.Presenter onCreatePresenter() {
            return new MainPresenter();
        }

}

Kotlin에서는 다음과 같이 사용이 됩니다.

class MainFragment : BasePresenterFragment<MainContract.View, MainContract.Presenter>(), MainContract.View {

    override fun onCreatePresenter() = MainPresenter()
}


마무리

제가 사용하는 MVP에 대해서 정리를 해보았는데 유용할지는 모르겠습니다.

일단 제 기준으로 작성하였고, MVP의 형태도 기존과 다르게 변경하였습니다.

원래는 구글의 방식대로 Activity에서 초기화하고 Fragment(View)가 받아서 동작하게 하는 형태였지만 생각해보면 Activity에 무조건 Fragment가 붙는 형태가 아님을 생각하게 되어 오늘의 형태가 되었습니다.

이전 글은 나중에 수정할 예정입니다.

그래서 Fragment, Activity 어디에서든 초기화할 수 있는 Presenter의 형태를 만들게 되었습니다.

다음 글에서는 Kotlin으로 작성한 BaseRecyclerView Base 코드를 살펴보도록 하겠습니다.

감사합니다.


kotlin 관련 글 더 보기


Tae-hwan

Android, Kotlin .. Create a content development.